fix(linker): deref uninit TCB

The bug is described for `x86_64` and Linux but is the same for other architectures and on Redox.

`Tcb::current()` is used to retrieve the current TCB, which is done by by reading `fs:[0x10]`. The TCB layout describes that at offset `0x10`, there is `tcb_ptr: *mut GenericTcb<...>`, which is nothing more but a pointer to itself.

This is fine as otherwise a system call would be required to get the TCB (`arch_prctl(ARCH_GET_FS)` on Linux).

However, this is problematic as the function may be called when the FS base is not set, and in that case the expected output of the function should be [`None`], but we don't currently handle that.

To fix this, any code paths that maybe call this function on an uninitialized TCB are be switched to call `current_slow()`. Which just uses `arch_prctl(ARCH_GET_FS)` to get the FS base and check if it's non-zero.

Signed-off-by: Anhad Singh <andypython@protonmail.com>
This commit is contained in:
Anhad Singh
2024-11-25 16:09:06 +11:00
parent 00746c2566
commit 37dbf5cbb2
3 changed files with 45 additions and 5 deletions
+3 -3
View File
@@ -47,7 +47,7 @@ pub unsafe extern "C" fn dlopen(cfilename: *const c_char, flags: c_int) -> *mut
))
};
let tcb = match Tcb::current() {
let tcb = match Tcb::current_slow() {
Some(tcb) => tcb,
None => {
ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
@@ -83,7 +83,7 @@ pub unsafe extern "C" fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *m
let symbol_str = str::from_utf8_unchecked(CStr::from_ptr(symbol).to_bytes());
let tcb = match Tcb::current() {
let tcb = match Tcb::current_slow() {
Some(tcb) => tcb,
None => {
ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
@@ -110,7 +110,7 @@ pub unsafe extern "C" fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *m
#[no_mangle]
pub unsafe extern "C" fn dlclose(handle: *mut c_void) -> c_int {
let tcb = match Tcb::current() {
let tcb = match Tcb::current_slow() {
Some(tcb) => tcb,
None => {
ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
+2 -2
View File
@@ -167,7 +167,7 @@ impl Linker {
)?;
unsafe {
let tcb = match Tcb::current() {
let tcb = match Tcb::current_slow() {
Some(some) => some,
None => Tcb::new(self.tls_size)?,
};
@@ -230,7 +230,7 @@ impl Linker {
if self.next_tls_module_id == 1 {
// Hack to allocate TCB on the first TLS module
unsafe {
if Tcb::current().is_none() {
if Tcb::current_slow().is_none() {
let tcb = Tcb::new(master.offset).expect_notls("failed to allocate TCB");
tcb.activate();
}
+40
View File
@@ -112,6 +112,46 @@ impl Tcb {
Some(&mut *GenericTcb::<OsSpecific>::current_ptr()?.cast())
}
// FIXME(andypython): move to platform/
pub unsafe fn current_slow() -> Option<&'static mut Self> {
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
{
use sc::nr::ARCH_PRCTL;
const ARCH_GET_FS: usize = 0x1003;
let mut fs_base = 0usize;
sc::syscall!(ARCH_PRCTL, ARCH_GET_FS, &mut fs_base as *mut usize);
if fs_base == 0 {
None
} else {
Some(&mut *(fs_base as *mut Self))
}
}
#[cfg(all(target_os = "redox", target_arch = "x86_64"))]
{
let mut env = syscall::EnvRegisters::default();
let file = syscall::open(
"/scheme/thisproc/current/regs/env",
syscall::O_CLOEXEC | syscall::O_RDONLY,
)
.expect_notls("failed to open handle for process registers");
let _ = syscall::read(file, &mut env).expect_notls("failed to read fsbase");
let _ = syscall::close(file);
if env.fsbase == 0 {
return None;
} else {
return Some(&mut *(env.fsbase as *mut Self));
}
}
// FIXME(andypython): Implement and test on other platforms
}
/// A slice for all of the TLS data
pub unsafe fn tls(&self) -> Option<&'static mut [u8]> {
if self.tls_end.is_null() || self.tls_len == 0 {