diff --git a/lib/libc/sys/procctl.2 b/lib/libc/sys/procctl.2 --- a/lib/libc/sys/procctl.2 +++ b/lib/libc/sys/procctl.2 @@ -29,7 +29,7 @@ .\" .\" $FreeBSD$ .\" -.Dd July 1, 2021 +.Dd September 2, 2021 .Dt PROCCTL 2 .Os .Sh NAME @@ -599,6 +599,62 @@ .It Dv PROC_NO_NEW_PRIVS_ENABLE .It Dv PROC_NO_NEW_PRIVS_DISABLE .El +.It Dv PROC_WXORX_CTL +Controls the 'write exclusive against execution' permissions for the +mappings in the process address space. +It overrides the global settings established by the +.Dv kern.elf{32/64}.allow_wx +sysctl, +and corresponding bit in the elf control note, see +.Xr elfctl 1 . +.Pp +The +.Fa data +parameter must point to the integer variable holding one of the +following values: +.Bl -tag -width PROC_WXORX_ENABLE_ON_EXEC +.It Dv PROC_WXORX_DISABLE +Enable creation of mappings that have both write and execute +protection attributes, in the specified process' address space. +.It Dv PROC_WXORX_ENABLE_ON_EXEC +In the new address space created by +.Xr execve 2 , +disallow creation of mappings that have both write and execute +permissions. +.El +.Pp +Once creation of writeable and executable mappings is allowed, +it is impossible (and pointless) to disallow it. +The only way to ensure the absence of such mappings after they +were enabled in a given process, is to set the +.Dv PROC_WXORX_ENABLE_ON_EXEC +flag and +.Xr execve 2 +an image. +.It Dv PROC_WXORX_STATUS +Returns the current status of the 'write exclusive against execution' +enforcement for the specified process. +The +.Dv data +parameter must point to the integer variable, where one of the +following values is written: +.Bl -tag -width PROC_WXORX_ENABLE_ON_EXEC +.It Dv PROC_WXORX_DISABLE +Creation of simultaneously writable and executable mapping is permitted, +otherwise the process cannot create such mappings. +.It Dv PROC_WXORX_ENABLE_ON_EXEC +After +.Xr execve 2 , +the new address space should disallow creation of simultaneously +writable and executable mappings. +.El +.Pp +Additionally, if the address space of the process disallows +creation of simultaneously writable and executable mappings and +it is guaranteed that no such mapping was created since address space +creation, the +.Dv PROC_WXORX_ENFORCE +flag is set in the returned value. .El .Sh x86 MACHINE-SPECIFIC REQUESTS .Bl -tag -width PROC_KPTI_STATUS @@ -648,6 +704,12 @@ and via other system mechanisms. As such, it should not be utilized to reliably protect cryptographic keying material or other confidential data. +.Pp +Note that processes can trivially bypass the 'no simultaneously +writable and executable mappings' policy by first marking some mapping +as writeable and write code to it, then removing write and adding +execute permission. +This may be legitimately required by some programs, such as JIT compilers. .Sh RETURN VALUES If an error occurs, a value of -1 is returned and .Va errno diff --git a/sys/compat/freebsd32/freebsd32_misc.c b/sys/compat/freebsd32/freebsd32_misc.c --- a/sys/compat/freebsd32/freebsd32_misc.c +++ b/sys/compat/freebsd32/freebsd32_misc.c @@ -3643,6 +3643,7 @@ case PROC_TRACE_CTL: case PROC_TRAPCAP_CTL: case PROC_NO_NEW_PRIVS_CTL: + case PROC_WXORX_CTL: error = copyin(PTRIN(uap->data), &flags, sizeof(flags)); if (error != 0) return (error); @@ -3677,6 +3678,7 @@ case PROC_TRACE_STATUS: case PROC_TRAPCAP_STATUS: case PROC_NO_NEW_PRIVS_STATUS: + case PROC_WXORX_STATUS: data = &flags; break; case PROC_PDEATHSIG_CTL: @@ -3709,6 +3711,7 @@ case PROC_TRACE_STATUS: case PROC_TRAPCAP_STATUS: case PROC_NO_NEW_PRIVS_STATUS: + case PROC_WXORX_STATUS: if (error == 0) error = copyout(&flags, uap->data, sizeof(flags)); break; diff --git a/sys/kern/imgact_elf.c b/sys/kern/imgact_elf.c --- a/sys/kern/imgact_elf.c +++ b/sys/kern/imgact_elf.c @@ -1216,7 +1216,8 @@ */ if (imgp->credential_setid) { PROC_LOCK(imgp->proc); - imgp->proc->p_flag2 &= ~(P2_ASLR_ENABLE | P2_ASLR_DISABLE); + imgp->proc->p_flag2 &= ~(P2_ASLR_ENABLE | P2_ASLR_DISABLE | + P2_WXORX_DISABLE | P2_WXORX_ENABLE_EXEC); PROC_UNLOCK(imgp->proc); } if ((sv->sv_flags & SV_ASLR) == 0 || @@ -1239,7 +1240,9 @@ imgp->map_flags |= MAP_ASLR_IGNSTART; } - if (!__elfN(allow_wx) && (fctl0 & NT_FREEBSD_FCTL_WXNEEDED) == 0) + if ((!__elfN(allow_wx) && (fctl0 & NT_FREEBSD_FCTL_WXNEEDED) == 0 && + (imgp->proc->p_flag2 & P2_WXORX_DISABLE) == 0) || + (imgp->proc->p_flag2 & P2_WXORX_ENABLE_EXEC) != 0) imgp->map_flags |= MAP_WXORX; error = exec_new_vmspace(imgp, sv); diff --git a/sys/kern/kern_fork.c b/sys/kern/kern_fork.c --- a/sys/kern/kern_fork.c +++ b/sys/kern/kern_fork.c @@ -493,7 +493,8 @@ p2->p_flag2 = p1->p_flag2 & (P2_ASLR_DISABLE | P2_ASLR_ENABLE | P2_ASLR_IGNSTART | P2_NOTRACE | P2_NOTRACE_EXEC | P2_PROTMAX_ENABLE | P2_PROTMAX_DISABLE | P2_TRAPCAP | - P2_STKGAP_DISABLE | P2_STKGAP_DISABLE_EXEC | P2_NO_NEW_PRIVS); + P2_STKGAP_DISABLE | P2_STKGAP_DISABLE_EXEC | P2_NO_NEW_PRIVS | + P2_WXORX_DISABLE | P2_WXORX_ENABLE_EXEC); p2->p_swtick = ticks; if (p1->p_flag & P_PROFIL) startprofclock(p2); diff --git a/sys/kern/kern_procctl.c b/sys/kern/kern_procctl.c --- a/sys/kern/kern_procctl.c +++ b/sys/kern/kern_procctl.c @@ -591,6 +591,71 @@ return (0); } +static int +wxorx_ctl(struct thread *td, struct proc *p, int state) +{ + struct vmspace *vm; + vm_map_t map; + + PROC_LOCK_ASSERT(p, MA_OWNED); + if ((p->p_flag & P_WEXIT) != 0) + return (ESRCH); + + switch (state) { + case PROC_WXORX_DISABLE: + p->p_flag2 |= P2_WXORX_DISABLE; + _PHOLD(p); + PROC_UNLOCK(p); + vm = vmspace_acquire_ref(p); + if (vm != NULL) { + map = &vm->vm_map; + vm_map_lock(map); + map->flags &= ~MAP_WXORX; + vm_map_unlock(map); + vmspace_free(vm); + } + PROC_LOCK(p); + _PRELE(p); + break; + case PROC_WXORX_ENABLE_ON_EXEC: + p->p_flag2 |= P2_WXORX_ENABLE_EXEC; + break; + default: + return (EINVAL); + } + + return (0); +} + +static int +wxorx_status(struct thread *td, struct proc *p, int *data) +{ + struct vmspace *vm; + int d; + + PROC_LOCK_ASSERT(p, MA_OWNED); + if ((p->p_flag & P_WEXIT) != 0) + return (ESRCH); + + d = 0; + if ((p->p_flag2 & P2_WXORX_DISABLE) != 0) + d |= PROC_WXORX_DISABLE; + if ((p->p_flag2 & P2_WXORX_ENABLE_EXEC) != 0) + d |= PROC_WXORX_ENABLE_ON_EXEC; + _PHOLD(p); + PROC_UNLOCK(p); + vm = vmspace_acquire_ref(p); + if (vm != NULL) { + if ((vm->vm_map.flags & MAP_WXORX) != 0) + d |= PROC_WXORX_ENFORCE; + vmspace_free(vm); + } + PROC_LOCK(p); + _PRELE(p); + *data = d; + return (0); +} + #ifndef _SYS_SYSPROTO_H_ struct procctl_args { idtype_t idtype; @@ -623,6 +688,7 @@ case PROC_TRACE_CTL: case PROC_TRAPCAP_CTL: case PROC_NO_NEW_PRIVS_CTL: + case PROC_WXORX_CTL: error = copyin(uap->data, &flags, sizeof(flags)); if (error != 0) return (error); @@ -655,6 +721,7 @@ case PROC_TRACE_STATUS: case PROC_TRAPCAP_STATUS: case PROC_NO_NEW_PRIVS_STATUS: + case PROC_WXORX_STATUS: data = &flags; break; case PROC_PDEATHSIG_CTL: @@ -686,6 +753,7 @@ case PROC_TRACE_STATUS: case PROC_TRAPCAP_STATUS: case PROC_NO_NEW_PRIVS_STATUS: + case PROC_WXORX_STATUS: if (error == 0) error = copyout(&flags, uap->data, sizeof(flags)); break; @@ -739,6 +807,10 @@ return (no_new_privs_ctl(td, p, *(int *)data)); case PROC_NO_NEW_PRIVS_STATUS: return (no_new_privs_status(td, p, data)); + case PROC_WXORX_CTL: + return (wxorx_ctl(td, p, *(int *)data)); + case PROC_WXORX_STATUS: + return (wxorx_status(td, p, data)); default: return (EINVAL); } @@ -771,6 +843,8 @@ case PROC_PDEATHSIG_STATUS: case PROC_NO_NEW_PRIVS_CTL: case PROC_NO_NEW_PRIVS_STATUS: + case PROC_WXORX_CTL: + case PROC_WXORX_STATUS: if (idtype != P_PID) return (EINVAL); } @@ -821,6 +895,8 @@ case PROC_TRACE_STATUS: case PROC_TRAPCAP_STATUS: case PROC_NO_NEW_PRIVS_STATUS: + case PROC_WXORX_CTL: + case PROC_WXORX_STATUS: tree_locked = false; break; default: diff --git a/sys/sys/proc.h b/sys/sys/proc.h --- a/sys/sys/proc.h +++ b/sys/sys/proc.h @@ -838,6 +838,8 @@ #define P2_ITSTOPPED 0x00002000 #define P2_PTRACEREQ 0x00004000 /* Active ptrace req */ #define P2_NO_NEW_PRIVS 0x00008000 /* Ignore setuid */ +#define P2_WXORX_DISABLE 0x00010000 /* WX mappings enabled */ +#define P2_WXORX_ENABLE_EXEC 0x00020000 /* WXORX enabled after exec */ /* Flags protected by proctree_lock, kept in p_treeflags. */ #define P_TREE_ORPHANED 0x00000001 /* Reparented, on orphan list */ diff --git a/sys/sys/procctl.h b/sys/sys/procctl.h --- a/sys/sys/procctl.h +++ b/sys/sys/procctl.h @@ -65,6 +65,8 @@ #define PROC_STACKGAP_STATUS 18 /* query stack gap */ #define PROC_NO_NEW_PRIVS_CTL 19 /* disable setuid/setgid */ #define PROC_NO_NEW_PRIVS_STATUS 20 /* query suid/sgid disabled status */ +#define PROC_WXORX_CTL 21 /* control W^X */ +#define PROC_WXORX_STATUS 22 /* query W^X */ /* Operations for PROC_SPROTECT (passed in integer arg). */ #define PPROT_OP(x) ((x) & 0xf) @@ -146,6 +148,10 @@ #define PROC_NO_NEW_PRIVS_ENABLE 1 #define PROC_NO_NEW_PRIVS_DISABLE 2 +#define PROC_WXORX_DISABLE 0x0001 +#define PROC_WXORX_ENABLE_ON_EXEC 0x0002 +#define PROC_WXORX_ENFORCE 0x80000000 + #ifndef _KERNEL __BEGIN_DECLS int procctl(idtype_t, id_t, int, void *); diff --git a/usr.bin/proccontrol/proccontrol.1 b/usr.bin/proccontrol/proccontrol.1 --- a/usr.bin/proccontrol/proccontrol.1 +++ b/usr.bin/proccontrol/proccontrol.1 @@ -28,7 +28,7 @@ .\" .\" $FreeBSD$ .\" -.Dd July 2, 2021 +.Dd September 2, 2021 .Dt PROCCONTROL 1 .Os .Sh NAME @@ -72,6 +72,8 @@ .It Ar nonewprivs Controls disabling the setuid and sgid bits for .Xr execve 2 . +.It Ar wxorx +Controls the write exclusive execute mode for mappings. .It Ar kpti Controls the KPTI enable, AMD64 only. .It Ar la48 diff --git a/usr.bin/proccontrol/proccontrol.c b/usr.bin/proccontrol/proccontrol.c --- a/usr.bin/proccontrol/proccontrol.c +++ b/usr.bin/proccontrol/proccontrol.c @@ -46,6 +46,7 @@ MODE_PROTMAX, MODE_STACKGAP, MODE_NO_NEW_PRIVS, + MODE_WXORX, #ifdef PROC_KPTI_CTL MODE_KPTI, #endif @@ -85,7 +86,7 @@ { fprintf(stderr, "Usage: proccontrol -m (aslr|protmax|trace|trapcap|" - "stackgap|nonewprivs"KPTI_USAGE LA_USAGE") [-q] " + "stackgap|nonewprivs|wxorx"KPTI_USAGE LA_USAGE") [-q] " "[-s (enable|disable)] [-p pid | command]\n"); exit(1); } @@ -116,6 +117,8 @@ mode = MODE_STACKGAP; else if (strcmp(optarg, "nonewprivs") == 0) mode = MODE_NO_NEW_PRIVS; + else if (strcmp(optarg, "wxorx") == 0) + mode = MODE_WXORX; #ifdef PROC_KPTI_CTL else if (strcmp(optarg, "kpti") == 0) mode = MODE_KPTI; @@ -178,7 +181,11 @@ error = procctl(P_PID, pid, PROC_STACKGAP_STATUS, &arg); break; case MODE_NO_NEW_PRIVS: - error = procctl(P_PID, pid, PROC_NO_NEW_PRIVS_STATUS, &arg); + error = procctl(P_PID, pid, PROC_NO_NEW_PRIVS_STATUS, + &arg); + break; + case MODE_WXORX: + error = procctl(P_PID, pid, PROC_WXORX_STATUS, &arg); break; #ifdef PROC_KPTI_CTL case MODE_KPTI: @@ -280,6 +287,17 @@ break; } break; + case MODE_WXORX: + if ((arg & PROC_WXORX_DISABLE) != 0) + printf("disabled"); + else + printf("enabled"); + if ((arg & PROC_WXORX_ENABLE_ON_EXEC) != 0) + printf(", enabled on exec"); + if ((arg & PROC_WXORX_ENFORCE) != 0) + printf(", enforced"); + printf("\n"); + break; #ifdef PROC_KPTI_CTL case MODE_KPTI: switch (arg & ~PROC_KPTI_STATUS_ACTIVE) { @@ -349,7 +367,13 @@ case MODE_NO_NEW_PRIVS: arg = enable ? PROC_NO_NEW_PRIVS_ENABLE : PROC_NO_NEW_PRIVS_DISABLE; - error = procctl(P_PID, pid, PROC_NO_NEW_PRIVS_CTL, &arg); + error = procctl(P_PID, pid, PROC_NO_NEW_PRIVS_CTL, + &arg); + break; + case MODE_WXORX: + arg = enable ? PROC_WXORX_ENABLE_ON_EXEC : + PROC_WXORX_DISABLE; + error = procctl(P_PID, pid, PROC_WXORX_CTL, &arg); break; #ifdef PROC_KPTI_CTL case MODE_KPTI: