Skip to main content

starnix_core/vdso/
vdso_loader.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::arch::vdso::VDSO_SIGRETURN_NAME;
6use crate::mm::memory::MemoryObject;
7use fidl_fuchsia_io as fio;
8use starnix_uapi::errors::Errno;
9use starnix_uapi::{errno, from_status_like_fdio};
10use std::sync::{Arc, LazyLock};
11
12pub static ZX_TIME_VALUES_MEMORY: LazyLock<Arc<MemoryObject>> = LazyLock::new(|| {
13    load_time_values_memory().expect(
14        "Could not find time values VMO! Please ensure /boot/kernel was routed to the starnix kernel.",
15    )
16});
17
18pub struct Vdso {
19    pub memory: Arc<MemoryObject>,
20    pub sigreturn_offset: u64,
21}
22
23impl Vdso {
24    pub fn new() -> Self {
25        let memory = load_vdso_from_file().expect("Couldn't read vDSO from disk");
26        let sigreturn_offset = match VDSO_SIGRETURN_NAME {
27            Some(name) => get_sigreturn_offset(&memory, name)
28                .expect("Couldn't find sigreturn trampoline code in vDSO"),
29            None => 0,
30        };
31
32        Self { memory, sigreturn_offset }
33    }
34
35    pub fn new_arch32() -> Option<Self> {
36        let maybe_memory = load_vdso_arch32_from_file();
37        if maybe_memory.is_err() {
38            return None;
39        }
40        let memory = maybe_memory.unwrap();
41        let sigreturn_offset = match VDSO_SIGRETURN_NAME {
42            Some(name) => get_sigreturn32_offset(&memory, name)
43                .expect("Couldn't find sigreturn trampoline code in arch32 vDSO"),
44            None => 0,
45        };
46
47        Some(Self { memory, sigreturn_offset })
48    }
49}
50
51fn sync_open_in_namespace(
52    path: &str,
53    flags: fio::Flags,
54) -> Result<fio::DirectorySynchronousProxy, Errno> {
55    let (client, server) = fidl::Channel::create();
56    let dir_proxy = fio::DirectorySynchronousProxy::new(client);
57
58    let namespace = fdio::Namespace::installed().map_err(|_| errno!(EINVAL))?;
59    namespace.open(path, flags, server).map_err(|_| errno!(ENOENT))?;
60    Ok(dir_proxy)
61}
62
63/// Reads the vDSO file and returns the backing VMO.
64fn load_vdso_from_file() -> Result<Arc<MemoryObject>, Errno> {
65    const VDSO_FILENAME: &str = "libvdso.so";
66    const VDSO_LOCATION: &str = "/pkg/data";
67
68    let dir_proxy = sync_open_in_namespace(VDSO_LOCATION, fio::PERM_READABLE)?;
69    let vdso_vmo = syncio::directory_open_vmo(
70        &dir_proxy,
71        VDSO_FILENAME,
72        fio::VmoFlags::READ,
73        zx::MonotonicInstant::INFINITE,
74    )
75    .map_err(|status| from_status_like_fdio!(status))?;
76
77    Ok(Arc::new(MemoryObject::from(vdso_vmo)))
78}
79
80/// Reads the vDSO file and returns the backing VMO.
81fn load_vdso_arch32_from_file() -> Result<Arc<MemoryObject>, Errno> {
82    const VDSO_FILENAME: &str = "libvdso_arch32.so";
83    const VDSO_LOCATION: &str = "/pkg/data";
84
85    let dir_proxy = sync_open_in_namespace(VDSO_LOCATION, fio::PERM_READABLE)?;
86    let vdso_vmo = syncio::directory_open_vmo(
87        &dir_proxy,
88        VDSO_FILENAME,
89        fio::VmoFlags::READ,
90        zx::MonotonicInstant::INFINITE,
91    )
92    .map_err(|status| from_status_like_fdio!(status))?;
93
94    Ok(Arc::new(MemoryObject::from(vdso_vmo)))
95}
96
97fn load_time_values_memory() -> Result<Arc<MemoryObject>, Errno> {
98    const FILENAME: &str = "time_values";
99    const DIR: &str = "/boot/kernel";
100
101    let (client, server) = fidl::Channel::create();
102    let dir_proxy = fio::DirectorySynchronousProxy::new(client);
103
104    let namespace = fdio::Namespace::installed().map_err(|_| errno!(EINVAL))?;
105    namespace.open(DIR, fuchsia_fs::PERM_READABLE, server).map_err(|_| errno!(ENOENT))?;
106
107    let vmo = syncio::directory_open_vmo(
108        &dir_proxy,
109        FILENAME,
110        fio::VmoFlags::READ,
111        zx::MonotonicInstant::INFINITE,
112    )
113    .map_err(|status| from_status_like_fdio!(status))?;
114
115    // Check that the time values VMO is the expected size of 1 page. If it is not,
116    // panic the kernel, as it means that the size of the time values VMO has changed
117    // and the starnix vDSO linker script at //src/starnix/kernel/vdso/vdso.ld should
118    // be updated.
119    let vmo_size = vmo.get_size().expect("failed to get time values VMO size");
120    let expected_size = 0x1000u64;
121    if vmo_size != expected_size {
122        panic!(
123            "time values VMO has unexpected size; got {:?}, expected {:?}",
124            vmo_size, expected_size
125        );
126    }
127    Ok(Arc::new(MemoryObject::from(vmo)))
128}
129
130fn get_string_index(string_table: &[u8], value: &[u8]) -> Option<usize> {
131    for (position, window) in string_table.windows(value.len()).enumerate() {
132        if window == value {
133            return Some(position);
134        }
135    }
136    None
137}
138
139fn get_sigreturn_offset(vdso_memory: &MemoryObject, sigreturn_name: &[u8]) -> Result<u64, Errno> {
140    let vdso_vmo = vdso_memory.as_vmo().ok_or_else(|| errno!(EINVAL))?;
141    let dyn_section = elf_parse::Elf64DynSection::from_vmo(vdso_vmo).map_err(|_| errno!(EINVAL))?;
142    let symtab = dyn_section
143        .dynamic_entry_with_tag(elf_parse::Elf64DynTag::Symtab)
144        .ok_or_else(|| errno!(EINVAL))?;
145    let strtab = dyn_section
146        .dynamic_entry_with_tag(elf_parse::Elf64DynTag::Strtab)
147        .ok_or_else(|| errno!(EINVAL))?;
148    let strsz = dyn_section
149        .dynamic_entry_with_tag(elf_parse::Elf64DynTag::Strsz)
150        .ok_or_else(|| errno!(EINVAL))?;
151
152    // Find the name of the signal trampoline in the string table and store the index.
153    let strtab_bytes = vdso_vmo
154        .read_to_vec(strtab.value, strsz.value)
155        .map_err(|status| from_status_like_fdio!(status))?;
156    let strtab_idx =
157        get_string_index(&strtab_bytes, sigreturn_name).ok_or_else(|| errno!(ENOENT))?;
158
159    const SYM_ENTRY_SIZE: usize = std::mem::size_of::<elf_parse::Elf64Sym>();
160
161    // In the symbolic table, find a symbol with a name index pointing to the name we're looking for.
162    let mut symtab_offset = symtab.value;
163    loop {
164        let sym_entry = vdso_vmo
165            .read_to_object::<elf_parse::Elf64Sym>(symtab_offset)
166            .map_err(|status| from_status_like_fdio!(status))?;
167        if sym_entry.st_name as usize == strtab_idx {
168            return Ok(sym_entry.st_value);
169        }
170        symtab_offset += SYM_ENTRY_SIZE as u64;
171    }
172}
173
174fn get_sigreturn32_offset(vdso_memory: &MemoryObject, sigreturn_name: &[u8]) -> Result<u64, Errno> {
175    let vdso_vmo = vdso_memory.as_vmo().ok_or_else(|| errno!(EINVAL))?;
176    let dyn_section =
177        elf_parse::Elf64DynSection::from_vmo_with_arch32(vdso_vmo).map_err(|_| errno!(EINVAL))?;
178    let symtab = dyn_section
179        .dynamic_entry_with_tag(elf_parse::Elf64DynTag::Symtab)
180        .ok_or_else(|| errno!(EINVAL))?;
181    let strtab = dyn_section
182        .dynamic_entry_with_tag(elf_parse::Elf64DynTag::Strtab)
183        .ok_or_else(|| errno!(EINVAL))?;
184    let strsz = dyn_section
185        .dynamic_entry_with_tag(elf_parse::Elf64DynTag::Strsz)
186        .ok_or_else(|| errno!(EINVAL))?;
187
188    // Find the name of the signal trampoline in the string table and store the index.
189    let strtab_bytes = vdso_vmo
190        .read_to_vec(strtab.value, strsz.value)
191        .map_err(|status| from_status_like_fdio!(status))?;
192    let strtab_idx =
193        get_string_index(&strtab_bytes, sigreturn_name).ok_or_else(|| errno!(ENOENT))?;
194
195    const SYM_ENTRY_SIZE: usize = std::mem::size_of::<elf_parse::Elf32Sym>();
196
197    // In the symbolic table, find a symbol with a name index pointing to the name we're looking for.
198    let mut symtab_offset = symtab.value;
199    loop {
200        let sym_entry = vdso_vmo
201            .read_to_object::<elf_parse::Elf32Sym>(symtab_offset)
202            .map_err(|status| from_status_like_fdio!(status))?;
203        if sym_entry.st_name as usize == strtab_idx {
204            return Ok(sym_entry.st_value as u64);
205        }
206        symtab_offset += SYM_ENTRY_SIZE as u64;
207    }
208}