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