1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
|
From 83e769b98e8e820cd1f58385a7f975b39d2087d6 Mon Sep 17 00:00:00 2001
From: Dan Gohman <dev@sunfishcode.online>
Date: Tue, 7 Jan 2025 12:02:11 -0800
Subject: [PATCH] Check for a missing `DT_HASH` section in the VDSO parser
(#1254)
* Check for a missing `DT_HASH` section in the VDSO parser
Fix a missing check for a null hash table pointer, which can happen
if the vDSO lacks a `DT_HASH` entry.
Also, incorporate the changes in the latest upstream version of thee
reference parse_vdso.c code.
* Fix type differences on s390x.
* Add more tests.
* Remove an over-aggressive check.
* Fix the name of getcpu on riscv64.
FG: replace DT_GNU_HASH constant with value, can be removed once linux-raw-sys
>= 0.6.6 is available in Debian
Signed-off-by: Fabian Grünbichler <debian@fabian.gruenbichler.email>
---
src/backend/linux_raw/vdso.rs | 84 +++++++++++++++++++-------
src/backend/linux_raw/vdso_wrappers.rs | 2 +-
2 files changed, 64 insertions(+), 22 deletions(-)
--- a/src/backend/linux_raw/vdso.rs
+++ b/src/backend/linux_raw/vdso.rs
@@ -1,7 +1,7 @@
//! Parse the Linux vDSO.
//!
//! The following code is transliterated from
-//! tools/testing/selftests/vDSO/parse_vdso.c in Linux 5.11, which is licensed
+//! tools/testing/selftests/vDSO/parse_vdso.c in Linux 6.12, which is licensed
//! with Creative Commons Zero License, version 1.0,
//! available at <https://creativecommons.org/publicdomain/zero/1.0/legalcode>
//!
@@ -20,6 +20,11 @@
use core::ptr::{null, null_mut};
use linux_raw_sys::elf::*;
+#[cfg(target_arch = "s390x")]
+type ElfHashEntry = u64;
+#[cfg(not(target_arch = "s390x"))]
+type ElfHashEntry = u32;
+
pub(super) struct Vdso {
// Load information
load_addr: *const Elf_Ehdr,
@@ -29,17 +34,19 @@
// Symbol table
symtab: *const Elf_Sym,
symstrings: *const u8,
- bucket: *const u32,
- chain: *const u32,
- nbucket: u32,
- //nchain: u32,
+ bucket: *const ElfHashEntry,
+ chain: *const ElfHashEntry,
+ nbucket: ElfHashEntry,
+ //nchain: ElfHashEntry,
// Version table
versym: *const u16,
verdef: *const Elf_Verdef,
}
-// Straight from the ELF specification.
+/// Straight from the ELF specification...and then tweaked slightly, in order to
+/// avoid a few clang warnings.
+/// (And then translated to Rust).
fn elf_hash(name: &CStr) -> u32 {
let mut h: u32 = 0;
for b in name.to_bytes() {
@@ -91,11 +98,6 @@
let mut found_vaddr = false;
for i in 0..hdr.e_phnum {
let phdr = &*pt.add(i as usize);
- if phdr.p_flags & PF_W != 0 {
- // Don't trust any vDSO that claims to be loading writable
- // segments into memory.
- return None;
- }
if phdr.p_type == PT_LOAD && !found_vaddr {
// The segment should be readable and executable, because it
// contains the symbol table and the function bodies.
@@ -129,7 +131,7 @@
}
// Fish out the useful bits of the dynamic table.
- let mut hash: *const u32 = null();
+ let mut hash: *const ElfHashEntry = null();
vdso.symstrings = null();
vdso.symtab = null();
vdso.versym = null();
@@ -152,8 +154,10 @@
.as_ptr();
}
DT_HASH => {
- hash = check_raw_pointer::<u32>(vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _)?
- .as_ptr();
+ hash = check_raw_pointer::<ElfHashEntry>(
+ vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _
+ )?
+ .as_ptr();
}
DT_VERSYM => {
vdso.versym =
@@ -176,8 +180,12 @@
}
i = i.checked_add(1)?;
}
- // The upstream code checks `symstrings`, `symtab`, and `hash` for
- // null; here, `check_raw_pointer` has already done that.
+ // `check_raw_pointer` will have checked these pointers for null,
+ // however they could still be null if the expected dynamic table
+ // entries are absent.
+ if vdso.symstrings.is_null() || vdso.symtab.is_null() || hash.is_null() {
+ return None; // Failed
+ }
if vdso.verdef.is_null() {
vdso.versym = null();
@@ -260,16 +268,20 @@
// SAFETY: The pointers in `self` must be valid.
unsafe {
- let mut chain = *self.bucket.add((name_hash % self.nbucket) as usize);
+ let mut chain = *self
+ .bucket
+ .add((ElfHashEntry::from(name_hash) % self.nbucket) as usize);
- while chain != STN_UNDEF {
+ while chain != ElfHashEntry::from(STN_UNDEF) {
let sym = &*self.symtab.add(chain as usize);
// Check for a defined global or weak function w/ right name.
//
- // The reference parser in Linux's parse_vdso.c requires
- // symbols to have type `STT_FUNC`, but on powerpc64, the vDSO
- // uses `STT_NOTYPE`, so allow that too.
+ // Accept `STT_NOTYPE` in addition to `STT_FUNC` for the symbol
+ // type, for compatibility with some versions of Linux on
+ // PowerPC64. See [this commit] in Linux for more background.
+ //
+ // [this commit]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/tools/testing/selftests/vDSO/parse_vdso.c?id=0161bd38c24312853ed5ae9a425a1c41c4ac674a
if (ELF_ST_TYPE(sym.st_info) != STT_FUNC &&
ELF_ST_TYPE(sym.st_info) != STT_NOTYPE)
|| (ELF_ST_BIND(sym.st_info) != STB_GLOBAL
@@ -311,3 +323,33 @@
self.base_plus(elf_addr.wrapping_add(self.pv_offset))
}
}
+
+#[cfg(linux_raw)]
+#[test]
+#[ignore] // Until rustix is updated to the new vDSO format.
+fn test_vdso() {
+ let vdso = Vdso::new().unwrap();
+ assert!(!vdso.symtab.is_null());
+ assert!(!vdso.symstrings.is_null());
+
+ #[cfg(target_arch = "x86_64")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_clock_gettime"));
+ #[cfg(target_arch = "arm")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_clock_gettime64"));
+ #[cfg(target_arch = "aarch64")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6.39"), cstr!("__kernel_clock_gettime"));
+ #[cfg(target_arch = "x86")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_clock_gettime64"));
+ #[cfg(target_arch = "riscv64")]
+ let ptr = vdso.sym(cstr!("LINUX_4.15"), cstr!("__vdso_clock_gettime"));
+ #[cfg(target_arch = "powerpc64")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6.15"), cstr!("__kernel_clock_gettime"));
+ #[cfg(target_arch = "s390x")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6.29"), cstr!("__kernel_clock_gettime"));
+ #[cfg(any(target_arch = "mips", target_arch = "mips32r6"))]
+ let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_clock_gettime64"));
+ #[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
+ let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_clock_gettime"));
+
+ assert!(!ptr.is_null());
+}
--- a/src/backend/linux_raw/vdso_wrappers.rs
+++ b/src/backend/linux_raw/vdso_wrappers.rs
@@ -556,7 +556,7 @@
#[cfg(target_arch = "x86")]
let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_getcpu"));
#[cfg(target_arch = "riscv64")]
- let ptr = vdso.sym(cstr!("LINUX_4.15"), cstr!("__kernel_getcpu"));
+ let ptr = vdso.sym(cstr!("LINUX_4.15"), cstr!("__vdso_getcpu"));
#[cfg(target_arch = "powerpc64")]
let ptr = vdso.sym(cstr!("LINUX_2.6.15"), cstr!("__kernel_getcpu"));
|