diff --git a/redox-rt/src/arch/i686.rs b/redox-rt/src/arch/i686.rs
index d8a6ced45a798511bcf1d1f0e53166448e1c7c07..fce06454c1583eac1aac6f4f15fa0630c17bd302 100644
--- a/redox-rt/src/arch/i686.rs
+++ b/redox-rt/src/arch/i686.rs
@@ -272,3 +272,11 @@ pub unsafe fn manually_enter_trampoline() {
         sc_saved_eip = const offset_of!(Sigcontrol, saved_ip),
     );
 }
+/// Get current stack pointer, weak granularity guarantees.
+pub fn current_sp() -> usize {
+    let sp: usize;
+    unsafe {
+        core::arch::asm!("mov {}, esp", out(reg) sp);
+    }
+    sp
+}
diff --git a/redox-rt/src/arch/x86_64.rs b/redox-rt/src/arch/x86_64.rs
index 9462313dde9836066def2f7915420d2f318d1c3c..fc425eecf5a4fc27fcac4b95336a24eb71456d17 100644
--- a/redox-rt/src/arch/x86_64.rs
+++ b/redox-rt/src/arch/x86_64.rs
@@ -375,3 +375,12 @@ pub unsafe fn manually_enter_trampoline() {
         sc_saved_rip = const offset_of!(Sigcontrol, saved_ip),
     );
 }
+
+/// Get current stack pointer, weak granularity guarantees.
+pub fn current_sp() -> usize {
+    let sp: usize;
+    unsafe {
+        core::arch::asm!("mov {}, rsp", out(reg) sp);
+    }
+    sp
+}
diff --git a/redox-rt/src/signal.rs b/redox-rt/src/signal.rs
index af56b33473836e56946fee4ce52d0c1d268acb26..313519cff9beb147133bb601c20737317a873029 100644
--- a/redox-rt/src/signal.rs
+++ b/redox-rt/src/signal.rs
@@ -2,7 +2,7 @@ use core::cell::{Cell, UnsafeCell};
 use core::ffi::c_int;
 use core::sync::atomic::{AtomicUsize, Ordering};
 
-use syscall::{RawAction, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGQUIT, SIGSEGV, SIGSYS, SIGTRAP, SIGXCPU, SIGXFSZ};
+use syscall::{RawAction, ENOMEM, EPERM, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGQUIT, SIGSEGV, SIGSYS, SIGTRAP, SIGXCPU, SIGXFSZ};
 use syscall::{Error, Result, SetSighandlerData, SigProcControl, Sigcontrol, SigcontrolFlags, EINVAL, SIGCHLD, SIGCONT, SIGKILL, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGWINCH, data::AtomicU64};
 
 use crate::{arch::*, Tcb};
@@ -213,6 +213,7 @@ pub struct Sigaction {
     pub mask: u64,
     pub flags: SigactionFlags,
 }
+
 impl Sigaction {
     fn ip(&self) -> usize {
         unsafe {
@@ -446,3 +447,53 @@ pub fn current_setsighandler_struct() -> SetSighandlerData {
         proc_control_addr: &PROC_CONTROL_STRUCT as *const SigProcControl as usize,
     }
 }
+
+#[derive(Clone, Copy, Default, PartialEq)]
+pub enum Sigaltstack {
+    #[default]
+    Disabled,
+
+    Enabled { onstack: bool, base: *mut (), size: usize },
+}
+pub unsafe fn sigaltstack(new: Option<&Sigaltstack>, old_out: Option<&mut Sigaltstack>) -> Result<()> {
+    let _g = tmp_disable_signals();
+    let tcb = &mut *Tcb::current().unwrap().os_specific.arch.get();
+
+    let old = if tcb.altstack_bottom == 0 && tcb.altstack_top == usize::MAX {
+        Sigaltstack::Disabled
+    } else {
+        Sigaltstack::Enabled {
+            base: tcb.altstack_bottom as *mut (),
+            size: tcb.altstack_top - tcb.altstack_bottom,
+            onstack: (tcb.altstack_bottom..tcb.altstack_top).contains(&crate::arch::current_sp()),
+        }
+    };
+
+    if matches!(old, Sigaltstack::Enabled { onstack: true, .. }) && new != Some(&old) {
+        return Err(Error::new(EPERM));
+    }
+
+    if let Some(old_out) = old_out {
+        *old_out = old;
+    }
+    if let Some(new) = new {
+        match *new {
+            Sigaltstack::Disabled => {
+                tcb.altstack_bottom = 0;
+                tcb.altstack_top = usize::MAX;
+            }
+            Sigaltstack::Enabled { onstack: true, .. } => return Err(Error::new(EINVAL)),
+            Sigaltstack::Enabled { base, size, onstack: false } => {
+                if size < MIN_SIGALTSTACK_SIZE {
+                    return Err(Error::new(ENOMEM));
+                }
+
+                tcb.altstack_bottom = base as usize;
+                tcb.altstack_top = base as usize + size;
+            }
+        }
+    }
+    Ok(())
+}
+
+pub const MIN_SIGALTSTACK_SIZE: usize = 8192;
diff --git a/src/header/signal/mod.rs b/src/header/signal/mod.rs
index c015f3f9cb4587cea91d2c9dda8c5565bacca522..3b15084c2301bb13ce55c8b0aec7d3d8dab31448 100644
--- a/src/header/signal/mod.rs
+++ b/src/header/signal/mod.rs
@@ -127,16 +127,9 @@ pub unsafe extern "C" fn sigaddset(set: *mut sigset_t, signo: c_int) -> c_int {
 
 #[no_mangle]
 pub unsafe extern "C" fn sigaltstack(ss: *const stack_t, old_ss: *mut stack_t) -> c_int {
-    if !ss.is_null() {
-        if (*ss).ss_flags != SS_DISABLE as c_int {
-            return errno::EINVAL;
-        }
-        if (*ss).ss_size < MINSIGSTKSZ {
-            return errno::ENOMEM;
-        }
-    }
-
-    Sys::sigaltstack(ss, old_ss)
+    Sys::sigaltstack(ss.as_ref(), old_ss.as_mut())
+        .map(|()| 0)
+        .or_minus_one_errno()
 }
 
 #[no_mangle]
diff --git a/src/platform/linux/signal.rs b/src/platform/linux/signal.rs
index 0fbc8493e9e4f010440999809a37b5f0d79e9c2c..8762af6a6decac7bab45523bfe82319c704ffc6b 100644
--- a/src/platform/linux/signal.rs
+++ b/src/platform/linux/signal.rs
@@ -65,8 +65,13 @@ impl PalSignal for Sys {
         .map(|_| ())
     }
 
-    unsafe fn sigaltstack(ss: *const stack_t, old_ss: *mut stack_t) -> c_int {
-        e(syscall!(SIGALTSTACK, ss, old_ss)) as c_int
+    unsafe fn sigaltstack(ss: Option<&stack_t>, old_ss: Option<&mut stack_t>) -> Result<(), Errno> {
+        e_raw(syscall!(
+            SIGALTSTACK,
+            ss.map_or_else(core::ptr::null, |x| x as *const _),
+            old_ss.map_or_else(core::ptr::null_mut, |x| x as *mut _)
+        ))
+        .map(|_| ())
     }
 
     unsafe fn sigpending(set: *mut sigset_t) -> c_int {
diff --git a/src/platform/pal/signal.rs b/src/platform/pal/signal.rs
index d85c35d955a0ecc98582006100a5a253668979da..d478ec16c82d0fd5359375d441e15956d91c9013 100644
--- a/src/platform/pal/signal.rs
+++ b/src/platform/pal/signal.rs
@@ -25,7 +25,7 @@ pub trait PalSignal: Pal {
         oact: Option<&mut sigaction>,
     ) -> Result<(), Errno>;
 
-    unsafe fn sigaltstack(ss: *const stack_t, old_ss: *mut stack_t) -> c_int;
+    unsafe fn sigaltstack(ss: Option<&stack_t>, old_ss: Option<&mut stack_t>) -> Result<(), Errno>;
 
     unsafe fn sigpending(set: *mut sigset_t) -> c_int;
 
diff --git a/src/platform/redox/libredox.rs b/src/platform/redox/libredox.rs
index 7001c07d72fc764f0ccebfc5212c549141151cc9..cdcf54611048295ddee04dbb78c4033c1bd21601 100644
--- a/src/platform/redox/libredox.rs
+++ b/src/platform/redox/libredox.rs
@@ -148,10 +148,7 @@ pub unsafe extern "C" fn redox_dup2_v1(
 }
 #[no_mangle]
 pub unsafe extern "C" fn redox_read_v1(fd: usize, dst_base: *mut u8, dst_len: usize) -> RawResult {
-    Error::mux(posix_read(
-        fd,
-        slice::from_raw_parts_mut(dst_base, dst_len),
-    ))
+    Error::mux(posix_read(fd, slice::from_raw_parts_mut(dst_base, dst_len)))
 }
 #[no_mangle]
 pub unsafe extern "C" fn redox_write_v1(
diff --git a/src/platform/redox/signal.rs b/src/platform/redox/signal.rs
index de8d1ee2b31e86ce35b5a1aa1672ac60d7192fc7..b495c590fd9c08bd4d82fbbf82281636fe361b9a 100644
--- a/src/platform/redox/signal.rs
+++ b/src/platform/redox/signal.rs
@@ -1,5 +1,5 @@
 use core::mem;
-use redox_rt::signal::{Sigaction, SigactionFlags, SigactionKind, SignalHandler};
+use redox_rt::signal::{Sigaction, SigactionFlags, SigactionKind, Sigaltstack, SignalHandler};
 use syscall::{self, Result};
 
 use super::{
@@ -11,7 +11,7 @@ use crate::{
         errno::{EINVAL, ENOSYS},
         signal::{
             sigaction, siginfo_t, sigset_t, stack_t, SA_SIGINFO, SIG_BLOCK, SIG_DFL, SIG_IGN,
-            SIG_SETMASK, SIG_UNBLOCK,
+            SIG_SETMASK, SIG_UNBLOCK, SS_DISABLE, SS_ONSTACK,
         },
         sys_time::{itimerval, ITIMER_REAL},
         time::timespec,
@@ -176,8 +176,51 @@ impl PalSignal for Sys {
         Ok(())
     }
 
-    unsafe fn sigaltstack(ss: *const stack_t, old_ss: *mut stack_t) -> c_int {
-        unimplemented!()
+    unsafe fn sigaltstack(
+        new_c: Option<&stack_t>,
+        old_c: Option<&mut stack_t>,
+    ) -> Result<(), Errno> {
+        let new = new_c
+            .map(|c_stack| {
+                let flags = usize::try_from(c_stack.ss_flags).map_err(|_| Errno(EINVAL))?;
+                if flags != flags & (SS_DISABLE | SS_ONSTACK) {
+                    return Err(Errno(EINVAL));
+                }
+
+                Ok(if flags & SS_DISABLE == SS_DISABLE {
+                    Sigaltstack::Disabled
+                } else {
+                    Sigaltstack::Enabled {
+                        onstack: false,
+                        base: c_stack.ss_sp.cast(),
+                        size: c_stack.ss_size,
+                    }
+                })
+            })
+            .transpose()?;
+
+        let mut old = old_c.as_ref().map(|_| Sigaltstack::default());
+        redox_rt::signal::sigaltstack(new.as_ref(), old.as_mut())?;
+
+        if let (Some(old_c_stack), Some(old)) = (old_c, old) {
+            *old_c_stack = match old {
+                Sigaltstack::Disabled => stack_t {
+                    ss_sp: core::ptr::null_mut(),
+                    ss_size: 0,
+                    ss_flags: SS_DISABLE.try_into().unwrap(),
+                },
+                Sigaltstack::Enabled {
+                    onstack,
+                    base,
+                    size,
+                } => stack_t {
+                    ss_sp: base.cast(),
+                    ss_size: size,
+                    ss_flags: SS_ONSTACK.try_into().unwrap(),
+                },
+            };
+        }
+        Ok(())
     }
 
     unsafe fn sigpending(set: *mut sigset_t) -> c_int {
diff --git a/src/pthread/mod.rs b/src/pthread/mod.rs
index f6751cb510c42e03e49b9ff0e8c2c272a4df7f12..cfc56dd485afb5b21c985751fdcc80c0aedf546e 100644
--- a/src/pthread/mod.rs
+++ b/src/pthread/mod.rs
@@ -139,8 +139,8 @@ pub(crate) unsafe fn create(
     let mut current_sigmask = 0_u64;
     #[cfg(target_os = "redox")]
     {
-        current_sigmask = redox_rt::signal::get_sigmask()
-            .expect("failed to obtain sigprocmask for caller");
+        current_sigmask =
+            redox_rt::signal::get_sigmask().expect("failed to obtain sigprocmask for caller");
     }
 
     // Create a locked mutex, unlocked by the thread after it has started.