procctl(2): Add PROC_WXMAP_CTL/STATUS

It allows to override kern.elf{32,64}.allow_wx on per-process basis.
In particular, it makes it possible to run binaries without PT_GNU_STACK
and without elfctl note while allow_wx = 0.

Reviewed by:	brooks, emaste, markj
Sponsored by:	The FreeBSD Foundation
MFC after:	1 week
Differential revision:	https://reviews.freebsd.org/D31779
This commit is contained in:
Konstantin Belousov 2021-09-02 03:59:10 +03:00
parent 1349891a0e
commit 796a8e1ad1
7 changed files with 157 additions and 4 deletions

View file

@ -29,7 +29,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd July 1, 2021
.Dd September 2, 2021
.Dt PROCCTL 2
.Os
.Sh NAME
@ -599,6 +599,62 @@ following values is written:
.It Dv PROC_NO_NEW_PRIVS_ENABLE
.It Dv PROC_NO_NEW_PRIVS_DISABLE
.El
.It Dv PROC_WXMAP_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 the 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_WX_MAPPINGS_DISALLOW_EXEC
.It Dv PROC_WX_MAPPINGS_PERMIT
Enable creation of mappings that have both write and execute
protection attributes, in the specified process' address space.
.It Dv PROC_WX_MAPPINGS_DISALLOW_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_WX_MAPPINGS_DISALLOW_EXEC
flag and
.Xr execve 2
an image.
.It Dv PROC_WXMAP_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_WX_MAPPINGS_DISALLOW_EXEC
.It Dv PROC_WX_MAPPINGS_PERMIT
Creation of simultaneously writable and executable mapping is permitted,
otherwise the process cannot create such mappings.
.It Dv PROC_WX_MAPPINGS_DISALLOW_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 @@ feature, as it is bypassable both by the kernel and privileged processes,
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

View file

@ -3643,6 +3643,7 @@ freebsd32_procctl(struct thread *td, struct freebsd32_procctl_args *uap)
case PROC_TRACE_CTL:
case PROC_TRAPCAP_CTL:
case PROC_NO_NEW_PRIVS_CTL:
case PROC_WXMAP_CTL:
error = copyin(PTRIN(uap->data), &flags, sizeof(flags));
if (error != 0)
return (error);
@ -3677,6 +3678,7 @@ freebsd32_procctl(struct thread *td, struct freebsd32_procctl_args *uap)
case PROC_TRACE_STATUS:
case PROC_TRAPCAP_STATUS:
case PROC_NO_NEW_PRIVS_STATUS:
case PROC_WXMAP_STATUS:
data = &flags;
break;
case PROC_PDEATHSIG_CTL:
@ -3709,6 +3711,7 @@ freebsd32_procctl(struct thread *td, struct freebsd32_procctl_args *uap)
case PROC_TRACE_STATUS:
case PROC_TRAPCAP_STATUS:
case PROC_NO_NEW_PRIVS_STATUS:
case PROC_WXMAP_STATUS:
if (error == 0)
error = copyout(&flags, uap->data, sizeof(flags));
break;

View file

@ -1216,7 +1216,8 @@ __CONCAT(exec_, __elfN(imgact))(struct image_params *imgp)
*/
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 @@ __CONCAT(exec_, __elfN(imgact))(struct image_params *imgp)
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);

View file

@ -493,7 +493,8 @@ do_fork(struct thread *td, struct fork_req *fr, struct proc *p2, struct thread *
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);

View file

@ -591,6 +591,71 @@ stackgap_status(struct thread *td, struct proc *p, int *data)
return (0);
}
static int
wxmap_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_WX_MAPPINGS_PERMIT:
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_WX_MAPPINGS_DISALLOW_EXEC:
p->p_flag2 |= P2_WXORX_ENABLE_EXEC;
break;
default:
return (EINVAL);
}
return (0);
}
static int
wxmap_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_WX_MAPPINGS_PERMIT;
if ((p->p_flag2 & P2_WXORX_ENABLE_EXEC) != 0)
d |= PROC_WX_MAPPINGS_DISALLOW_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 @@ sys_procctl(struct thread *td, struct procctl_args *uap)
case PROC_TRACE_CTL:
case PROC_TRAPCAP_CTL:
case PROC_NO_NEW_PRIVS_CTL:
case PROC_WXMAP_CTL:
error = copyin(uap->data, &flags, sizeof(flags));
if (error != 0)
return (error);
@ -655,6 +721,7 @@ sys_procctl(struct thread *td, struct procctl_args *uap)
case PROC_TRACE_STATUS:
case PROC_TRAPCAP_STATUS:
case PROC_NO_NEW_PRIVS_STATUS:
case PROC_WXMAP_STATUS:
data = &flags;
break;
case PROC_PDEATHSIG_CTL:
@ -686,6 +753,7 @@ sys_procctl(struct thread *td, struct procctl_args *uap)
case PROC_TRACE_STATUS:
case PROC_TRAPCAP_STATUS:
case PROC_NO_NEW_PRIVS_STATUS:
case PROC_WXMAP_STATUS:
if (error == 0)
error = copyout(&flags, uap->data, sizeof(flags));
break;
@ -739,6 +807,10 @@ kern_procctl_single(struct thread *td, struct proc *p, int com, void *data)
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_WXMAP_CTL:
return (wxmap_ctl(td, p, *(int *)data));
case PROC_WXMAP_STATUS:
return (wxmap_status(td, p, data));
default:
return (EINVAL);
}
@ -771,6 +843,8 @@ kern_procctl(struct thread *td, idtype_t idtype, id_t id, int com, void *data)
case PROC_PDEATHSIG_STATUS:
case PROC_NO_NEW_PRIVS_CTL:
case PROC_NO_NEW_PRIVS_STATUS:
case PROC_WXMAP_CTL:
case PROC_WXMAP_STATUS:
if (idtype != P_PID)
return (EINVAL);
}
@ -821,6 +895,8 @@ kern_procctl(struct thread *td, idtype_t idtype, id_t id, int com, void *data)
case PROC_TRACE_STATUS:
case PROC_TRAPCAP_STATUS:
case PROC_NO_NEW_PRIVS_STATUS:
case PROC_WXMAP_CTL:
case PROC_WXMAP_STATUS:
tree_locked = false;
break;
default:

View file

@ -838,6 +838,8 @@ struct proc {
#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 */

View file

@ -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_WXMAP_CTL 21 /* control W^X */
#define PROC_WXMAP_STATUS 22 /* query W^X */
/* Operations for PROC_SPROTECT (passed in integer arg). */
#define PPROT_OP(x) ((x) & 0xf)
@ -146,6 +148,10 @@ struct procctl_reaper_kill {
#define PROC_NO_NEW_PRIVS_ENABLE 1
#define PROC_NO_NEW_PRIVS_DISABLE 2
#define PROC_WX_MAPPINGS_PERMIT 0x0001
#define PROC_WX_MAPPINGS_DISALLOW_EXEC 0x0002
#define PROC_WXORX_ENFORCE 0x80000000
#ifndef _KERNEL
__BEGIN_DECLS
int procctl(idtype_t, id_t, int, void *);