diff --git a/lib/libsys/pdfork.2 b/lib/libsys/pdfork.2 --- a/lib/libsys/pdfork.2 +++ b/lib/libsys/pdfork.2 @@ -83,7 +83,7 @@ .Fn pdfork can accept the .Fa pdflags: -.Bl -tag -width PD_CLOEXEC +.Bl -tag -width PD_PROCCTL_CAP .It Dv PD_DAEMON Instead of the default terminate-on-close behaviour, allow the process to live until it is explicitly killed with @@ -106,6 +106,14 @@ .It Dv PD_NOWAITPID The parent cannot obtain the child's status with .Xr waitpid 2 . +.It Dv PD_PROCCTL_CAP +Grant the process descriptor the +.Dv CAP_PROCCTL +right, which +.Xr procctl 2 +with +.Dv P_PROCDESC +requires and which is otherwise not granted. .El .Pp The diff --git a/lib/libsys/procctl.2 b/lib/libsys/procctl.2 --- a/lib/libsys/procctl.2 +++ b/lib/libsys/procctl.2 @@ -27,7 +27,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd April 21, 2025 +.Dd August 6, 2026 .Dt PROCCTL 2 .Os .Sh NAME @@ -65,8 +65,33 @@ .It Dv P_PGID Control processes belonging to the process group with the ID .Fa id . +.It Dv P_PROCDESC +Control the process referenced by the process descriptor +.Fa id . +The descriptor is required to have the +.Dv CAP_PROCCTL +right, which +.Xr pdfork 2 +grants only when created with the +.Dv PD_PROCCTL_CAP +flag. .El .Pp +Unlike the other identifier types, which name processes by numbers in a +global namespace, +.Dv P_PROCDESC +names a process by a descriptor the caller already holds. +It is therefore the only identifier type accepted in capability mode; +see +.Xr cap_enter 2 . +.Pp +In capability mode, moreover, only the commands that act solely on the named +process are permitted. +Commands that reach beyond it, such as the process reaping controls, are +denied and fail with +.Er ECAPMODE ; +this restriction is conservative and may be relaxed in the future. +.Pp The control request to perform is specified by the .Fa cmd argument. @@ -111,6 +136,8 @@ control and only honors the .Xr elfctl 1 flag and system-wide policy. +.Pp +This request is permitted in capability mode. .It Dv PROC_ASLR_STATUS Returns the current status of ASLR enablement for the target process. The @@ -127,6 +154,8 @@ the .Dv PROC_ASLR_ACTIVE flag is or-ed with the value listed above. +.Pp +This request is not permitted in capability mode. .It Dv PROC_LOGSIGEXIT_CTL Controls the logging of exits due to signals that would normally cause a core dump. @@ -149,6 +178,8 @@ MIB variable .Va kern.logsigexit . .El +.Pp +This request is not permitted in capability mode. .It Dv PROC_LOGSIGEXIT_STATUS Returns the current status of logging for the target process. The @@ -160,6 +191,8 @@ .It Dv PROC_LOGSIGEXIT_CTL_FORCE_DISABLE .It Dv PROC_LOGSIGEXIT_CTL_NOFORCE .El +.Pp +This request is not permitted in capability mode. .It Dv PROC_PROTMAX_CTL Controls the maximum protection used for .Xr mmap 2 @@ -207,6 +240,8 @@ flag takes precedence over this control. Executing a binary with this flag set will always use RWX as the implicit maximum protection. +.Pp +This request is not permitted in capability mode. .It Dv PROC_PROTMAX_STATUS Returns the current status of the implicit PROT_MAX control for the target process. @@ -224,6 +259,8 @@ control enabled, the .Dv PROC_PROTMAX_ACTIVE flag is or-ed with the value listed above. +.Pp +This request is not permitted in capability mode. .It Dv PROC_SPROTECT Set process protection state. This is used to mark a process as protected from being killed if the system @@ -251,6 +288,8 @@ mark all future child processes of each selected process as protected. Future child processes will also mark all of their future child processes. .El +.Pp +This request is not permitted in capability mode. .It Dv PROC_REAP_ACQUIRE Enable orphaned process reaping for future children of the current process. .Pp @@ -273,11 +312,15 @@ After system initialization, .Xr init 8 is the default reaper. +.Pp +This request is not permitted in capability mode. .It Dv PROC_REAP_RELEASE Disable orphaned process reaping for the current process. .Pp Any processes for whom the current process was the reaper are reassigned to the current process's reaper. +.Pp +This request is not permitted in capability mode. .It Dv PROC_REAP_STATUS Provides a consistent snapshot of information about the reaper of the specified process, @@ -332,6 +375,8 @@ returns the pid of one reaper child if there are any processes that can be reapead; otherwise, it is set to \-1. +.Pp +This request is not permitted in capability mode. .It Dv PROC_REAP_GETPIDS Queries the list of processes that can be reaped by the reaper of the specified process. @@ -405,6 +450,8 @@ .It Dv REAPER_PIDINFO_EXITING The reported process is in the process of exiting (but not yet a zombie). .El +.Pp +This request is not permitted in capability mode. .It Dv PROC_REAP_KILL Request to deliver a signal to some subset of the descendants of the reaper. The @@ -459,6 +506,8 @@ If no such process exists, the .Fa rk_fpid field is set to \-1. +.Pp +This request is not permitted in capability mode. .It Dv PROC_TRACE_CTL Enable or disable tracing of the specified process(es), according to the value of the integer argument. @@ -492,6 +541,8 @@ but the setting persists for the process even after .Xr execve 2 . .El +.Pp +This request is not permitted in capability mode. .It Dv PROC_TRACE_STATUS Returns the current tracing status for the specified process in the integer variable pointed to by @@ -507,6 +558,8 @@ If a debugger is attached, .Fa data is set to the pid of the debugger process. +.Pp +This request is not permitted in capability mode. .It Dv PROC_TRAPCAP_CTL Controls the capability mode sandbox actions for the specified sandboxed processes @@ -567,6 +620,8 @@ See .Xr capsicum 4 for more information about capability mode. +.Pp +This request is not permitted in capability mode. .It Dv PROC_TRAPCAP_STATUS Return the current status of raising .Dv SIGTRAP @@ -584,6 +639,8 @@ See the note about sysctl .Dv kern.trap_enotcap above, which gives independent global control of signal delivery. +.Pp +This request is not permitted in capability mode. .It Dv PROC_PDEATHSIG_CTL Request the delivery of a signal when the parent of the calling process exits. @@ -601,6 +658,8 @@ indicating the signal that should be delivered to the caller. Use zero to cancel a previously requested signal delivery. +.Pp +This request is not permitted in capability mode. .It Dv PROC_PDEATHSIG_STATUS Query the current signal number that will be delivered when the parent of the calling process exits. @@ -615,6 +674,8 @@ .Vt int . If signal delivery has not been requested, it will contain zero on return. +.Pp +This request is not permitted in capability mode. .It Dv PROC_STACKGAP_CTL Controls stack gaps in the specified process. A stack gap is one or more virtual memory pages at the end of the @@ -662,6 +723,8 @@ .Pp The stack gap state is inherited from the parent on .Xr fork 2 . +.Pp +This request is not permitted in capability mode. .It Dv PROC_STACKGAP_STATUS Returns the current stack gap state for the specified process. .Fa data @@ -690,6 +753,8 @@ However, the controls value can still be inherited by child processes, and executing a binary without this flag set will revert to the behavior specified by the control. +.Pp +This request is not permitted in capability mode. .It Dv PROC_NO_NEW_PRIVS_CTL Allows one to ignore the set-user-ID and set-group-ID bits on the program images activated by @@ -705,6 +770,8 @@ .El .Pp It is not possible to disable this control once it has been enabled. +.Pp +This request is not permitted in capability mode. .It Dv PROC_NO_NEW_PRIVS_STATUS Returns the current status of set-ID bits enablement for the target process. The @@ -715,6 +782,8 @@ .It Dv PROC_NO_NEW_PRIVS_ENABLE .It Dv PROC_NO_NEW_PRIVS_DISABLE .El +.Pp +This request is not permitted in capability mode. .It Dv PROC_WXMAP_CTL Controls the creation of mappings with both write and execute permissions in a process's address space. @@ -753,6 +822,8 @@ flag and .Xr execve 2 an image. +.Pp +This request is not permitted in capability mode. .It Dv PROC_WXMAP_STATUS Returns the current status of the controls over creation of mappings with both write and execute permissions for the specified process. @@ -777,6 +848,8 @@ creation, the .Dv PROC_WXORX_ENFORCE flag is set in the returned value. +.Pp +This request is not permitted in capability mode. .El .Sh x86 MACHINE-SPECIFIC REQUESTS .Bl -tag -width indent @@ -806,6 +879,8 @@ .Va PRIV_IO privilege can use this option. .El +.Pp +This request is not permitted in capability mode. .It Dv PROC_KPTI_STATUS Returns the current KPTI status for the specified process. .Fa data @@ -819,6 +894,8 @@ The status is or-ed with .Va PROC_KPTI_STATUS_ACTIVE if KPTI is active for the current address space of the process. +.Pp +This request is not permitted in capability mode. .El .Sh NOTES Disabling tracing on a process should not be considered a security @@ -854,14 +931,52 @@ The .Fa idtype argument specifies an unsupported identifier type. +.Pp +The +.Fa idtype +argument is +.Dv P_PROCDESC +and +.Fa id +is a descriptor of a type other than a process descriptor. .It Bq Er EPERM The calling process does not have permission to perform the requested operation on any of the selected processes. +.It Bq Er ECAPMODE +The calling process is in capability mode and the +.Fa idtype +argument is not +.Dv P_PROCDESC , +or the +.Fa cmd +is not one of those permitted in capability mode. +.It Bq Er EBADF +The +.Fa idtype +argument is +.Dv P_PROCDESC +and +.Fa id +is not a valid descriptor. +.It Bq Er ENOTCAPABLE +The +.Fa idtype +argument is +.Dv P_PROCDESC +and +.Fa id +lacks the +.Dv CAP_PROCCTL +right. .It Bq Er ESRCH No processes matched the requested .Fa idtype and .Fa id . +A +.Dv P_PROCDESC +descriptor also stops matching once the process it referenced has exited and +been reaped. .It Bq Er ESRCH No descendant processes can be found matching criteria specified in the .Dv PROC_REAP_KILL @@ -944,6 +1059,7 @@ .Xr ktrace 2 , .Xr mmap 2 , .Xr mprotect 2 , +.Xr pdfork 2 , .Xr ptrace 2 , .Xr wait 2 , .Xr capsicum 4 , diff --git a/share/man/man4/rights.4 b/share/man/man4/rights.4 --- a/share/man/man4/rights.4 +++ b/share/man/man4/rights.4 @@ -30,7 +30,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 22, 2025 +.Dd August 6, 2026 .Dt RIGHTS 4 .Os .Sh NAME @@ -501,6 +501,13 @@ .Dv CAP_READ and .Dv CAP_SEEK . +.It Dv CAP_PROCCTL +Permit +.Xr procctl 2 +with an +.Fa idtype +of +.Dv P_PROCDESC . .It Dv CAP_PWRITE An alias to .Dv CAP_SEEK diff --git a/sys/compat/freebsd32/freebsd32_sysent.c b/sys/compat/freebsd32/freebsd32_sysent.c --- a/sys/compat/freebsd32/freebsd32_sysent.c +++ b/sys/compat/freebsd32/freebsd32_sysent.c @@ -611,7 +611,7 @@ { .sy_narg = AS(accept4_args), .sy_call = (sy_call_t *)sys_accept4, .sy_auevent = AUE_ACCEPT, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 541 = accept4 */ { .sy_narg = AS(pipe2_args), .sy_call = (sy_call_t *)sys_pipe2, .sy_auevent = AUE_PIPE, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 542 = pipe2 */ { .sy_narg = AS(freebsd32_aio_mlock_args), .sy_call = (sy_call_t *)freebsd32_aio_mlock, .sy_auevent = AUE_AIO_MLOCK, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 543 = freebsd32_aio_mlock */ - { .sy_narg = AS(freebsd32_procctl_args), .sy_call = (sy_call_t *)freebsd32_procctl, .sy_auevent = AUE_PROCCTL, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 544 = freebsd32_procctl */ + { .sy_narg = AS(freebsd32_procctl_args), .sy_call = (sy_call_t *)freebsd32_procctl, .sy_auevent = AUE_PROCCTL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 544 = freebsd32_procctl */ { .sy_narg = AS(freebsd32_ppoll_args), .sy_call = (sy_call_t *)freebsd32_ppoll, .sy_auevent = AUE_POLL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 545 = freebsd32_ppoll */ { .sy_narg = AS(freebsd32_futimens_args), .sy_call = (sy_call_t *)freebsd32_futimens, .sy_auevent = AUE_FUTIMES, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 546 = freebsd32_futimens */ { .sy_narg = AS(freebsd32_utimensat_args), .sy_call = (sy_call_t *)freebsd32_utimensat, .sy_auevent = AUE_FUTIMESAT, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 547 = freebsd32_utimensat */ diff --git a/sys/kern/init_sysent.c b/sys/kern/init_sysent.c --- a/sys/kern/init_sysent.c +++ b/sys/kern/init_sysent.c @@ -610,7 +610,7 @@ { .sy_narg = AS(accept4_args), .sy_call = (sy_call_t *)sys_accept4, .sy_auevent = AUE_ACCEPT, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 541 = accept4 */ { .sy_narg = AS(pipe2_args), .sy_call = (sy_call_t *)sys_pipe2, .sy_auevent = AUE_PIPE, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 542 = pipe2 */ { .sy_narg = AS(aio_mlock_args), .sy_call = (sy_call_t *)sys_aio_mlock, .sy_auevent = AUE_AIO_MLOCK, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 543 = aio_mlock */ - { .sy_narg = AS(procctl_args), .sy_call = (sy_call_t *)sys_procctl, .sy_auevent = AUE_PROCCTL, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 544 = procctl */ + { .sy_narg = AS(procctl_args), .sy_call = (sy_call_t *)sys_procctl, .sy_auevent = AUE_PROCCTL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 544 = procctl */ { .sy_narg = AS(ppoll_args), .sy_call = (sy_call_t *)sys_ppoll, .sy_auevent = AUE_POLL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 545 = ppoll */ { .sy_narg = AS(futimens_args), .sy_call = (sy_call_t *)sys_futimens, .sy_auevent = AUE_FUTIMES, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 546 = futimens */ { .sy_narg = AS(utimensat_args), .sy_call = (sy_call_t *)sys_utimensat, .sy_auevent = AUE_FUTIMESAT, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 547 = utimensat */ 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 @@ -131,6 +131,8 @@ filecaps_fill(&fcaps); if ((uap->flags & PD_PTRACE_CAP) == 0) cap_rights_clear(&fcaps.fc_rights, CAP_PTRACE); + if ((uap->flags & PD_PROCCTL_CAP) == 0) + cap_rights_clear(&fcaps.fc_rights, CAP_PROCCTL); fr.fr_pd_fcaps = &fcaps; AUDIT_ARG_FFLAGS(uap->flags); /* @@ -236,6 +238,8 @@ filecaps_fill(&fcaps); if ((uap->pdflags & PD_PTRACE_CAP) == 0) cap_rights_clear(&fcaps.fc_rights, CAP_PTRACE); + if ((uap->pdflags & PD_PROCCTL_CAP) == 0) + cap_rights_clear(&fcaps.fc_rights, CAP_PROCCTL); fr.fr_pd_fcaps = &fcaps; error = fork1(td, &fr); if (error == 0) { 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 @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include #include @@ -40,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -1012,6 +1015,19 @@ bool copyout_on_error : 1; bool no_nonnull_data : 1; bool need_candebug : 1; + /* + * Whether the command may be issued in capability mode against a + * process descriptor. We err on the side of prohibiting: it is true + * only for commands audited to act solely on the named process, while + * reaping and group operations, which reach beyond it, leave it false. + * Some of these prohibitions may be revisited in the future. + * + * A finer design would limit which commands a given descriptor permits, + * the way cap_ioctls_limit(2) and cap_fcntls_limit(2) restrict the + * ioctl(2) and fcntl(2) requests allowed on a descriptor, rather than + * gating every command behind CAP_PROCCTL plus this one shared bit. + */ + bool cap_safe : 1; int copyin_sz; int copyout_sz; int (*exec)(struct thread *, struct proc *, void *); @@ -1021,39 +1037,39 @@ [PROC_SPROTECT] = { .lock_tree = PCTL_SLOCKED, .one_proc = false, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = sizeof(int), .copyout_sz = 0, .exec = protect_set, .copyout_on_error = false, }, [PROC_REAP_ACQUIRE] = { .lock_tree = PCTL_XLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = true, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = 0, .copyout_sz = 0, .exec = reap_acquire, .copyout_on_error = false, }, [PROC_REAP_RELEASE] = { .lock_tree = PCTL_XLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = true, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = 0, .copyout_sz = 0, .exec = reap_release, .copyout_on_error = false, }, [PROC_REAP_STATUS] = { .lock_tree = PCTL_SLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = 0, .copyout_sz = sizeof(struct procctl_reaper_status), .exec = reap_status, .copyout_on_error = false, }, [PROC_REAP_GETPIDS] = { .lock_tree = PCTL_SLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = sizeof(struct procctl_reaper_pids), .copyout_sz = 0, .exec = reap_getpids, .copyout_on_error = false, }, [PROC_REAP_KILL] = { .lock_tree = PCTL_SLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = sizeof(struct procctl_reaper_kill), .copyout_sz = sizeof(struct procctl_reaper_kill), .exec = reap_kill, .copyout_on_error = true, @@ -1061,109 +1077,109 @@ [PROC_TRACE_CTL] = { .lock_tree = PCTL_SLOCKED, .one_proc = false, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = true, + .need_candebug = true, .cap_safe = false, .copyin_sz = sizeof(int), .copyout_sz = 0, .exec = trace_ctl, .copyout_on_error = false, }, [PROC_TRACE_STATUS] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = 0, .copyout_sz = sizeof(int), .exec = trace_status, .copyout_on_error = false, }, [PROC_TRAPCAP_CTL] = { .lock_tree = PCTL_SLOCKED, .one_proc = false, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = true, + .need_candebug = true, .cap_safe = false, .copyin_sz = sizeof(int), .copyout_sz = 0, .exec = trapcap_ctl, .copyout_on_error = false, }, [PROC_TRAPCAP_STATUS] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = 0, .copyout_sz = sizeof(int), .exec = trapcap_status, .copyout_on_error = false, }, [PROC_PDEATHSIG_CTL] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = true, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = sizeof(int), .copyout_sz = 0, .exec = pdeathsig_ctl, .copyout_on_error = false, }, [PROC_PDEATHSIG_STATUS] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = true, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = 0, .copyout_sz = sizeof(int), .exec = pdeathsig_status, .copyout_on_error = false, }, [PROC_ASLR_CTL] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = true, + .need_candebug = true, .cap_safe = true, .copyin_sz = sizeof(int), .copyout_sz = 0, .exec = aslr_ctl, .copyout_on_error = false, }, [PROC_ASLR_STATUS] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = 0, .copyout_sz = sizeof(int), .exec = aslr_status, .copyout_on_error = false, }, [PROC_PROTMAX_CTL] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = true, + .need_candebug = true, .cap_safe = false, .copyin_sz = sizeof(int), .copyout_sz = 0, .exec = protmax_ctl, .copyout_on_error = false, }, [PROC_PROTMAX_STATUS] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = 0, .copyout_sz = sizeof(int), .exec = protmax_status, .copyout_on_error = false, }, [PROC_STACKGAP_CTL] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = true, + .need_candebug = true, .cap_safe = false, .copyin_sz = sizeof(int), .copyout_sz = 0, .exec = stackgap_ctl, .copyout_on_error = false, }, [PROC_STACKGAP_STATUS] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = 0, .copyout_sz = sizeof(int), .exec = stackgap_status, .copyout_on_error = false, }, [PROC_NO_NEW_PRIVS_CTL] = { .lock_tree = PCTL_SLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = true, + .need_candebug = true, .cap_safe = false, .copyin_sz = sizeof(int), .copyout_sz = 0, .exec = no_new_privs_ctl, .copyout_on_error = false, }, [PROC_NO_NEW_PRIVS_STATUS] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = 0, .copyout_sz = sizeof(int), .exec = no_new_privs_status, .copyout_on_error = false, }, [PROC_WXMAP_CTL] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = true, + .need_candebug = true, .cap_safe = false, .copyin_sz = sizeof(int), .copyout_sz = 0, .exec = wxmap_ctl, .copyout_on_error = false, }, [PROC_WXMAP_STATUS] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = 0, .copyout_sz = sizeof(int), .exec = wxmap_status, .copyout_on_error = false, }, [PROC_LOGSIGEXIT_CTL] = { .lock_tree = PCTL_SLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = true, + .need_candebug = true, .cap_safe = false, .copyin_sz = sizeof(int), .copyout_sz = 0, .exec = logsigexit_ctl, .copyout_on_error = false, }, [PROC_LOGSIGEXIT_STATUS] = { .lock_tree = PCTL_UNLOCKED, .one_proc = true, .esrch_is_einval = false, .no_nonnull_data = false, - .need_candebug = false, + .need_candebug = false, .cap_safe = false, .copyin_sz = 0, .copyout_sz = sizeof(int), .exec = logsigexit_status, .copyout_on_error = false, }, }; @@ -1180,9 +1196,25 @@ const struct procctl_cmd_info *cmd_info; int error, error1; - if (uap->com >= PROC_PROCCTL_MD_MIN) + /* + * In capability mode only P_PROCDESC is available: the other identifier + * types name a process through a global namespace the sandbox is not + * supposed to be able to reach into. + */ + if (IN_CAPABILITY_MODE(td) && uap->idtype != P_PROCDESC) + return (ECAPMODE); + + if (uap->com >= PROC_PROCCTL_MD_MIN) { + /* + * Machine-dependent commands have not been audited for + * capability mode, so deny them there for now, as with the + * commands below that are not cap_safe. + */ + if (IN_CAPABILITY_MODE(td)) + return (ECAPMODE); return (cpu_procctl(td, uap->idtype, uap->id, uap->com, uap->data)); + } if (uap->com <= 0 || uap->com >= nitems(procctl_cmds_info)) return (EINVAL); cmd_info = &procctl_cmds_info[uap->com]; @@ -1221,18 +1253,38 @@ return (error); } +/* + * A single-process caller returns on error; P_PGID skips the process on error. + */ +static int +kern_procctl_permit(struct thread *td, struct proc *p, + const struct procctl_cmd_info *cmd_info) +{ + PROC_LOCK_ASSERT(p, MA_OWNED); + return (cmd_info->need_candebug ? p_candebug(td, p) : p_cansee(td, p)); +} + int kern_procctl(struct thread *td, idtype_t idtype, id_t id, int com, void *data) { struct pgrp *pg; struct proc *p; + struct file *fp; const struct procctl_cmd_info *cmd_info; int error, first_error, ok; bool sapblk; MPASS(com > 0 && com < nitems(procctl_cmds_info)); cmd_info = &procctl_cmds_info[com]; - if (idtype != P_PID && cmd_info->one_proc) + /* + * In capability mode only commands that act solely on the named + * process are permitted; sys_procctl() has already limited the idtype + * to P_PROCDESC. + */ + if (IN_CAPABILITY_MODE(td) && !cmd_info->cap_safe) + return (ECAPMODE); + /* P_PID and P_PROCDESC name exactly one process. */ + if (idtype != P_PID && idtype != P_PROCDESC && cmd_info->one_proc) return (EINVAL); sapblk = false; @@ -1266,13 +1318,28 @@ EINVAL : ESRCH; break; } - error = cmd_info->need_candebug ? p_candebug(td, p) : - p_cansee(td, p); + error = kern_procctl_permit(td, p, cmd_info); } if (error == 0) error = kern_procctl_single(td, p, com, data); PROC_UNLOCK(p); break; + case P_PROCDESC: + if (cmd_info->lock_tree == PCTL_UNLOCKED) + sx_slock(&proctree_lock); + error = fget_procdesc(td, (int)id, &cap_procctl_rights, EINVAL, + &fp, NULL, &p); + if (cmd_info->lock_tree == PCTL_UNLOCKED) + sx_sunlock(&proctree_lock); + if (error == 0) { + error = kern_procctl_permit(td, p, cmd_info); + if (error == 0) + error = kern_procctl_single(td, p, com, data); + PROC_UNLOCK(p); + } + if (fp != NULL) + fdrop(fp, td); + break; case P_PGID: /* * Attempt to apply the operation to all members of the @@ -1292,8 +1359,7 @@ PROC_LOCK(p); if (p->p_state == PRS_NEW || p->p_state == PRS_ZOMBIE || - (cmd_info->need_candebug ? p_candebug(td, p) : - p_cansee(td, p)) != 0) { + kern_procctl_permit(td, p, cmd_info) != 0) { PROC_UNLOCK(p); continue; } diff --git a/sys/kern/subr_capability.c b/sys/kern/subr_capability.c --- a/sys/kern/subr_capability.c +++ b/sys/kern/subr_capability.c @@ -93,6 +93,7 @@ const cap_rights_t cap_pdkill_rights = CAP_RIGHTS_INITIALIZER(CAP_PDKILL); const cap_rights_t cap_pdwait_rights = CAP_RIGHTS_INITIALIZER(CAP_PDWAIT); const cap_rights_t cap_pread_rights = CAP_RIGHTS_INITIALIZER(CAP_PREAD); +const cap_rights_t cap_procctl_rights = CAP_RIGHTS_INITIALIZER(CAP_PROCCTL); const cap_rights_t cap_ptrace_rights = CAP_RIGHTS_INITIALIZER(CAP_PTRACE); const cap_rights_t cap_pwrite_rights = CAP_RIGHTS_INITIALIZER(CAP_PWRITE); const cap_rights_t cap_read_rights = CAP_RIGHTS_INITIALIZER(CAP_READ); diff --git a/sys/kern/sys_procdesc.c b/sys/kern/sys_procdesc.c --- a/sys/kern/sys_procdesc.c +++ b/sys/kern/sys_procdesc.c @@ -705,6 +705,8 @@ filecaps_fill(&fcaps); if ((flags & PD_PTRACE_CAP) == 0) cap_rights_clear(&fcaps.fc_rights, CAP_PTRACE); + if ((flags & PD_PROCCTL_CAP) == 0) + cap_rights_clear(&fcaps.fc_rights, CAP_PROCCTL); sx_xlock(&proctree_lock); error = pdopenpid1(td, pid, &pdf, fp); diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master --- a/sys/kern/syscalls.master +++ b/sys/kern/syscalls.master @@ -3020,7 +3020,7 @@ _In_ _Contains_long_ptr_ struct aiocb *aiocbp ); } -544 AUE_PROCCTL STD { +544 AUE_PROCCTL STD|CAPENABLED { int procctl( idtype_t idtype, id_t id, diff --git a/sys/sys/caprights.h b/sys/sys/caprights.h --- a/sys/sys/caprights.h +++ b/sys/sys/caprights.h @@ -95,6 +95,7 @@ extern const cap_rights_t cap_pdkill_rights; extern const cap_rights_t cap_pdwait_rights; extern const cap_rights_t cap_pread_rights; +extern const cap_rights_t cap_procctl_rights; extern const cap_rights_t cap_ptrace_rights; extern const cap_rights_t cap_pwrite_rights; extern const cap_rights_t cap_read_rights; diff --git a/sys/sys/capsicum.h b/sys/sys/capsicum.h --- a/sys/sys/capsicum.h +++ b/sys/sys/capsicum.h @@ -301,8 +301,9 @@ /* Allows ptrace(PT_PROCDESC) */ #define CAP_PTRACE CAPRIGHT(1, 0x0000000001000000ULL) +/* Allows procctl(P_PROCDESC) */ +#define CAP_PROCCTL CAPRIGHT(1, 0x0000000002000000ULL) -#define CAP_UNUSED1_26 CAPRIGHT(1, 0x0000000002000000ULL) #define CAP_UNUSED1_27 CAPRIGHT(1, 0x0000000004000000ULL) #define CAP_UNUSED1_28 CAPRIGHT(1, 0x0000000008000000ULL) #define CAP_UNUSED1_29 CAPRIGHT(1, 0x0000000010000000ULL) @@ -336,7 +337,7 @@ #define CAP_UNUSED1_57 CAPRIGHT(1, 0x0100000000000000ULL) /* All default bits for index 1. */ -#define CAP_ALL1 CAPRIGHT(1, 0x0000000001FFFFFFULL) +#define CAP_ALL1 CAPRIGHT(1, 0x0000000003FFFFFFULL) /* Backward compatibility. */ #define CAP_POLL_EVENT CAP_EVENT diff --git a/sys/sys/procdesc.h b/sys/sys/procdesc.h --- a/sys/sys/procdesc.h +++ b/sys/sys/procdesc.h @@ -161,10 +161,12 @@ #define PD_DAEMON 0x00000001 /* Don't exit when procdesc closes. */ #define PD_CLOEXEC 0x00000002 /* Close file descriptor on exec. */ #define PD_NOWAITPID 0x00000004 /* Reap without waitpid(). */ -#define PD_PTRACE_CAP 0x00000008 /* Allow PT_PROCDESC in cap mode. */ +#define PD_PTRACE_CAP 0x00000008 /* Allow ptrace(PT_PROCDESC) in cap mode. */ +#define PD_PROCCTL_CAP 0x00000010 /* Allow procctl(P_PROCDESC) in cap mode. */ #define PD_ALLOWED_AT_FORK \ - (PD_DAEMON | PD_CLOEXEC | PD_NOWAITPID | PD_PTRACE_CAP) -#define PD_ALLOWED_AT_OPENPID (PD_DAEMON | PD_CLOEXEC | PD_PTRACE_CAP) + (PD_DAEMON | PD_CLOEXEC | PD_NOWAITPID | PD_PTRACE_CAP | PD_PROCCTL_CAP) +#define PD_ALLOWED_AT_OPENPID \ + (PD_DAEMON | PD_CLOEXEC | PD_PTRACE_CAP | PD_PROCCTL_CAP) #endif /* !_SYS_PROCDESC_H_ */ diff --git a/sys/sys/wait.h b/sys/sys/wait.h --- a/sys/sys/wait.h +++ b/sys/sys/wait.h @@ -118,7 +118,8 @@ P_JAILID, /* A zone identifier. */ P_CTID, /* A (process) contract identifier. */ P_CPUID, /* CPU identifier. */ - P_PSETID /* Processor set identifier. */ + P_PSETID, /* Processor set identifier. */ + P_PROCDESC /* A process descriptor; only for procctl(2). */ } idtype_t; /* The type of id_t we are using. */ #if __BSD_VISIBLE