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_PTRACE_CAP .It Dv PD_DAEMON Instead of the default terminate-on-close behaviour, allow the process to live until it is explicitly killed with @@ -103,6 +103,12 @@ flag, closing that descriptor kills the process. .It Dv PD_CLOEXEC Set close-on-exec on process descriptor. +.It Dv PD_PTRACE_CAP +Allows the +.Xr ptrace 2 +request with the +.Dv PT_PROCDESC +modified on the resulting file descriptor. .El .Pp The @@ -158,6 +164,21 @@ .Er ESRCH error. .Pp +The +.Fn pdopenpid +request is allowed in the capability mode +.Pq see Xr capsicum 4 +.Bl -dash -compact +.It +when the target process is the child of the calling process +.It +when the calling process is the debugger of the target process. +.El +The debugger is attached to its target by +.Xr ptrace 2 +of other means, e.g. by debugging its real parent with +the follow mode on fork. +.Pp .Fn pdgetpid queries the process ID (PID) in the process descriptor .Fa fd . diff --git a/lib/libsys/ptrace.2 b/lib/libsys/ptrace.2 --- a/lib/libsys/ptrace.2 +++ b/lib/libsys/ptrace.2 @@ -131,10 +131,19 @@ Similarly, for operations affecting only a thread, the thread ID needs to be passed. .Pp +If the +.Fa request +argument is or-ed with the +.Dv PT_PROCDESC +flag, then the +.Fa pid +argument is interpreted as the process descriptor. +The call operates on the process referenced by the descriptor. +.Pp Still, for global operations, the ID of any thread can be used as the target, and system will perform the request on the process owning that thread. -If a thread operation got the process ID as +If a thread operation got the process ID or the process descriptor as .Fa pid , the system randomly selects a thread from among the threads owned by the process. @@ -180,6 +189,12 @@ process'. These requests will fail with error .Er ESRCH . +.It Dv security.bsd.ptrace_in_cap_mode +Setting this sysctl to true allows the +.Fn ptrace +requests, except +.Dv PT_ATTACH, +issued by the processes in capability mode. .It Dv securelevel and init The .Xr init 1 @@ -1044,6 +1059,60 @@ member. Note that the request and its result do not affect the returned value from the currently executed syscall, if any. +.It Dv PT_GET_CHILDREN +Returns a report describing the children of the process specified by +.Fa pid . +Only children visible to the current thread are reported. +.Pp +If the +.Fa addr +argument is +.Dv NULL , +the instantaneous number of children is returned as +the result of the request. +.Pp +If the +.Fa addr +argument is not +.Dv NULL , +it must point to the array of +.Bd -literal +struct ptrace_child { + pid_t pid; + int flags; +}; +.Ed +elements. +The size of the array in bytes must be passed in +.Fa data . +The +.Dv PT_GET_CHILDREN +implementation fills the array and returns the number of elements filled. +.Pp +For each element, the +.Va pid +member contains the PID of the corresponding child. +The +.Va flags +member may have the following flags set: +.Bl -tag -width PTCHLD_TRACED_BY_ME +.It Dv PTCHLD_TRACED +The child specified by the +.Va pid +member is traced. +.It Dv PTCHLD_TRACED_BY_ME +The child specified by the +.Va pid +member is traced +by the caller (not the +process specified by the +.Fa pid +argument to the function). +.It Dv PTCHLD_EXITED +The child process is exiting or is a zombie. +.It Dv PTCHLD_ORPHAN +The child process was temporarily reparented to its debugger. +.El .El .Sh PT_COREDUMP and PT_SC_REMOTE usage The process must be stopped before dumping or initiating a remote system call. @@ -1456,6 +1525,17 @@ .Fa pve_pathlen holds the minimum buffer size required on return. .El +.It Bq ECAPMODE +The process issuing the +.Fn ptrace +call is in capability mode, and either the +.Dv security.bsd.ptrace_in_cap_mode +tunable is set to +.Dv false, +or, when the tunable is set to +.Dv true , +the request is +.Dv PT_ATTACH . .El .Sh SEE ALSO .Xr execve 2 , diff --git a/sys/amd64/linux/linux_machdep.c b/sys/amd64/linux/linux_machdep.c --- a/sys/amd64/linux/linux_machdep.c +++ b/sys/amd64/linux/linux_machdep.c @@ -326,7 +326,8 @@ l_regset->fs_base = pcb->pcb_fsbase; l_regset->gs_base = pcb->pcb_gsbase; - error = kern_ptrace(td, PT_LWPINFO, pid, &lwpinfo, sizeof(lwpinfo)); + error = kern_ptrace(td, false, PT_LWPINFO, pid, &lwpinfo, + sizeof(lwpinfo)); if (error != 0) { linux_msg(td, "PT_LWPINFO failed with error %d", error); return (error); @@ -362,10 +363,10 @@ } if (LINUX_URO(addr, fs_base)) - return (kern_ptrace(td, PT_GETFSBASE, pid, data, 0)); + return (kern_ptrace(td, false, PT_GETFSBASE, pid, data, 0)); if (LINUX_URO(addr, gs_base)) - return (kern_ptrace(td, PT_GETGSBASE, pid, data, 0)); - if ((error = kern_ptrace(td, PT_GETREGS, pid, &b_reg, 0)) != 0) + return (kern_ptrace(td, false, PT_GETGSBASE, pid, data, 0)); + if ((error = kern_ptrace(td, false, PT_GETREGS, pid, &b_reg, 0)) != 0) return (error); bsd_to_linux_regset(&b_reg, ®); val = *(®.r15 + ((uintptr_t)addr / sizeof(reg.r15))); @@ -427,9 +428,9 @@ } if (LINUX_URO(addr, fs_base)) - return (kern_ptrace(td, PT_SETFSBASE, pid, data, 0)); + return (kern_ptrace(td, false, PT_SETFSBASE, pid, data, 0)); if (LINUX_URO(addr, gs_base)) - return (kern_ptrace(td, PT_SETGSBASE, pid, data, 0)); + return (kern_ptrace(td, false, PT_SETGSBASE, pid, data, 0)); for (i = 0; i < nitems(linux_segregs_off); i++) { if ((uintptr_t)addr == linux_segregs_off[i].reg) { if (linux_invalid_selector((uintptr_t)data)) @@ -438,13 +439,13 @@ return (EIO); } } - if ((error = kern_ptrace(td, PT_GETREGS, pid, &b_reg, 0)) != 0) + if ((error = kern_ptrace(td, false, PT_GETREGS, pid, &b_reg, 0)) != 0) return (error); bsd_to_linux_regset(&b_reg, ®); *(®.r15 + ((uintptr_t)addr / sizeof(reg.r15))) = (uint64_t)data; linux_to_bsd_regset(&b_reg1, ®); b_reg1.r_err = b_reg.r_err; b_reg1.r_trapno = b_reg.r_trapno; - return (kern_ptrace(td, PT_SETREGS, pid, &b_reg, 0)); + return (kern_ptrace(td, false, PT_SETREGS, pid, &b_reg, 0)); } #undef LINUX_URO 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 @@ -1031,6 +1031,7 @@ register_t args[nitems(td->td_sa.args)]; struct ptrace_sc_ret psr; int ptevents; + struct ptrace_child *children; } r; union { struct ptrace_io_desc32 piod; @@ -1046,6 +1047,7 @@ u_int pscr_args32[nitems(td->td_sa.args)]; void *addr; int data, error, i; + bool pd_mode; if (!allow_ptrace) return (ENOSYS); @@ -1056,6 +1058,9 @@ AUDIT_ARG_VALUE(uap->data); addr = &r; data = uap->data; + pd_mode = (uap->req & PT_PROCDESC) != 0; + uap->req &= ~PT_PROCDESC; + switch (uap->req) { case PT_GET_EVENT_MASK: case PT_GET_SC_ARGS: @@ -1173,6 +1178,14 @@ pscr_args[i] = pscr_args32[i]; r.sr.pscr_args = pscr_args; break; + case PT_GET_CHILDREN: + if (uap->addr == NULL) + addr = NULL; + else if (uap->data < 0) + error = EINVAL; + else + addr = &r.children; + break; case PTINTERNAL_FIRST ... PTINTERNAL_LAST: error = EINVAL; break; @@ -1183,7 +1196,7 @@ if (error) return (error); - error = kern_ptrace(td, uap->req, uap->pid, addr, data); + error = kern_ptrace(td, pd_mode, uap->req, uap->pid, addr, data); if (error) return (error); @@ -1242,6 +1255,13 @@ offsetof(struct ptrace_sc_remote32, pscr_ret), sizeof(r32.psr)); break; + case PT_GET_CHILDREN: + if (uap->addr != 0) { + error = copyout(r.children, uap->addr, + td->td_retval[0] * sizeof(struct ptrace_child)); + free(r.children, M_TEMP); + } + break; } return (error); 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 @@ -93,7 +93,7 @@ { .sy_narg = AS(setuid_args), .sy_call = (sy_call_t *)sys_setuid, .sy_auevent = AUE_SETUID, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 23 = setuid */ { .sy_narg = 0, .sy_call = (sy_call_t *)sys_getuid, .sy_auevent = AUE_GETUID, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 24 = getuid */ { .sy_narg = 0, .sy_call = (sy_call_t *)sys_geteuid, .sy_auevent = AUE_GETEUID, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 25 = geteuid */ - { .sy_narg = AS(freebsd32_ptrace_args), .sy_call = (sy_call_t *)freebsd32_ptrace, .sy_auevent = AUE_PTRACE, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 26 = freebsd32_ptrace */ + { .sy_narg = AS(freebsd32_ptrace_args), .sy_call = (sy_call_t *)freebsd32_ptrace, .sy_auevent = AUE_PTRACE, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 26 = freebsd32_ptrace */ { .sy_narg = AS(freebsd32_recvmsg_args), .sy_call = (sy_call_t *)freebsd32_recvmsg, .sy_auevent = AUE_RECVMSG, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 27 = freebsd32_recvmsg */ { .sy_narg = AS(freebsd32_sendmsg_args), .sy_call = (sy_call_t *)freebsd32_sendmsg, .sy_auevent = AUE_SENDMSG, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 28 = freebsd32_sendmsg */ { .sy_narg = AS(recvfrom_args), .sy_call = (sy_call_t *)sys_recvfrom, .sy_auevent = AUE_RECVFROM, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 29 = recvfrom */ @@ -670,6 +670,6 @@ { .sy_narg = AS(pdrfork_args), .sy_call = (sy_call_t *)sys_pdrfork, .sy_auevent = AUE_PDRFORK, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 600 = pdrfork */ { .sy_narg = AS(freebsd32_pdwait_args), .sy_call = (sy_call_t *)freebsd32_pdwait, .sy_auevent = AUE_PDWAIT, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 601 = freebsd32_pdwait */ { .sy_narg = AS(renameat2_args), .sy_call = (sy_call_t *)sys_renameat2, .sy_auevent = AUE_RENAMEAT, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 602 = renameat2 */ - { .sy_narg = AS(pdopenpid_args), .sy_call = (sy_call_t *)sys_pdopenpid, .sy_auevent = AUE_PDOPENPID, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 603 = pdopenpid */ + { .sy_narg = AS(pdopenpid_args), .sy_call = (sy_call_t *)sys_pdopenpid, .sy_auevent = AUE_PDOPENPID, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 603 = pdopenpid */ { .sy_narg = AS(pddupfd_args), .sy_call = (sy_call_t *)sys_pddupfd, .sy_auevent = AUE_NULL, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 604 = pddupfd */ }; diff --git a/sys/compat/linux/linux_ptrace.c b/sys/compat/linux/linux_ptrace.c --- a/sys/compat/linux/linux_ptrace.c +++ b/sys/compat/linux/linux_ptrace.c @@ -127,7 +127,8 @@ int error; saved_retval = td->td_retval[0]; - error = kern_ptrace(td, PT_LWPINFO, pid, &lwpinfo, sizeof(lwpinfo)); + error = kern_ptrace(td, false, PT_LWPINFO, pid, &lwpinfo, + sizeof(lwpinfo)); td->td_retval[0] = saved_retval; if (error != 0) { linux_msg(td, "PT_LWPINFO failed with error %d", error); @@ -161,7 +162,7 @@ { int error; - error = kern_ptrace(td, PT_READ_I, pid, addr, 0); + error = kern_ptrace(td, false, PT_READ_I, pid, addr, 0); if (error == 0) error = copyout(td->td_retval, data, sizeof(l_int)); else if (error == ENOMEM) @@ -222,7 +223,8 @@ pem->ptrace_flags &= ~LINUX_PTRACE_O_TRACEEXIT; } - return (kern_ptrace(td, PT_SET_EVENT_MASK, pid, &mask, sizeof(mask))); + return (kern_ptrace(td, false, PT_SET_EVENT_MASK, pid, &mask, + sizeof(mask))); } static int @@ -240,7 +242,8 @@ l_siginfo_t l_siginfo; int error, sig; - error = kern_ptrace(td, PT_LWPINFO, pid, &lwpinfo, sizeof(lwpinfo)); + error = kern_ptrace(td, false, PT_LWPINFO, pid, &lwpinfo, + sizeof(lwpinfo)); if (error != 0) { linux_msg(td, "PT_LWPINFO failed with error %d", error); return (error); @@ -266,7 +269,7 @@ struct linux_pt_regset l_regset; int error; - error = kern_ptrace(td, PT_GETREGS, pid, &b_reg, 0); + error = kern_ptrace(td, false, PT_GETREGS, pid, &b_reg, 0); if (error != 0) return (error); @@ -290,7 +293,7 @@ if (error != 0) return (error); linux_to_bsd_regset(&b_reg, &l_regset); - error = kern_ptrace(td, PT_SETREGS, pid, &b_reg, 0); + error = kern_ptrace(td, false, PT_SETREGS, pid, &b_reg, 0); return (error); } @@ -309,7 +312,7 @@ return (error); } - error = kern_ptrace(td, PT_GETREGS, pid, &b_reg, 0); + error = kern_ptrace(td, false, PT_GETREGS, pid, &b_reg, 0); if (error != 0) return (error); @@ -351,7 +354,7 @@ return (error); } - error = kern_ptrace(td, PT_GETFPREGS, pid, &b_fpreg, 0); + error = kern_ptrace(td, false, PT_GETFPREGS, pid, &b_fpreg, 0); if (error != 0) return (error); @@ -387,13 +390,15 @@ return (error); } - error = kern_ptrace(td, PT_GETXSTATE_INFO, pid, &info, sizeof(info)); + error = kern_ptrace(td, false, PT_GETXSTATE_INFO, pid, &info, + sizeof(info)); if (error != 0) return (error); xstate = malloc(info.xsave_len, M_LINUX, M_WAITOK | M_ZERO); - error = kern_ptrace(td, PT_GETXSTATE, pid, xstate, info.xsave_len); + error = kern_ptrace(td, false, PT_GETXSTATE, pid, xstate, + info.xsave_len); if (error != 0) { free(xstate, M_LINUX); return (error); @@ -454,7 +459,8 @@ struct syscall_info si; int error; - error = kern_ptrace(td, PT_LWPINFO, pid, &lwpinfo, sizeof(lwpinfo)); + error = kern_ptrace(td, false, PT_LWPINFO, pid, &lwpinfo, + sizeof(lwpinfo)); if (error != 0) { linux_msg(td, "PT_LWPINFO failed with error %d", error); return (error); @@ -465,7 +471,7 @@ if (lwpinfo.pl_flags & PL_FLAG_SCE) { si.op = LINUX_PTRACE_SYSCALL_INFO_ENTRY; si.entry.nr = lwpinfo.pl_syscall_code; - error = kern_ptrace(td, PTLINUX_GET_SC_ARGS, pid, + error = kern_ptrace(td, false, PTLINUX_GET_SC_ARGS, pid, si.entry.args, sizeof(si.entry.args)); if (error != 0) { linux_msg(td, @@ -474,7 +480,8 @@ } } else if (lwpinfo.pl_flags & PL_FLAG_SCX) { si.op = LINUX_PTRACE_SYSCALL_INFO_EXIT; - error = kern_ptrace(td, PT_GET_SC_RET, pid, &sr, sizeof(sr)); + error = kern_ptrace(td, false, PT_GET_SC_RET, pid, &sr, + sizeof(sr)); if (error != 0) { linux_msg(td, "PT_GET_SC_RET failed with error %d", @@ -505,7 +512,7 @@ si.op = LINUX_PTRACE_SYSCALL_INFO_NONE; } - error = kern_ptrace(td, PT_GETREGS, pid, &b_reg, 0); + error = kern_ptrace(td, false, PT_GETREGS, pid, &b_reg, 0); if (error != 0) return (error); @@ -534,7 +541,7 @@ switch (uap->req) { case LINUX_PTRACE_TRACEME: - error = kern_ptrace(td, PT_TRACE_ME, 0, 0, 0); + error = kern_ptrace(td, false, PT_TRACE_ME, 0, 0, 0); break; case LINUX_PTRACE_PEEKTEXT: case LINUX_PTRACE_PEEKDATA: @@ -552,13 +559,14 @@ break; case LINUX_PTRACE_POKETEXT: case LINUX_PTRACE_POKEDATA: - error = kern_ptrace(td, PT_WRITE_D, pid, addr, uap->data); + error = kern_ptrace(td, false, PT_WRITE_D, pid, addr, + uap->data); if (error != 0) goto out; /* * Linux expects this syscall to write 64 bits, not 32. */ - error = kern_ptrace(td, PT_WRITE_D, pid, + error = kern_ptrace(td, false, PT_WRITE_D, pid, (void *)(uap->addr + 4), uap->data >> 32); break; case LINUX_PTRACE_POKEUSER: @@ -568,16 +576,17 @@ error = map_signum(uap->data, &sig); if (error != 0) break; - error = kern_ptrace(td, PT_CONTINUE, pid, (void *)1, sig); + error = kern_ptrace(td, false, PT_CONTINUE, pid, (void *)1, + sig); break; case LINUX_PTRACE_KILL: - error = kern_ptrace(td, PT_KILL, pid, addr, uap->data); + error = kern_ptrace(td, false, PT_KILL, pid, addr, uap->data); break; case LINUX_PTRACE_SINGLESTEP: error = map_signum(uap->data, &sig); if (error != 0) break; - error = kern_ptrace(td, PT_STEP, pid, (void *)1, sig); + error = kern_ptrace(td, false, PT_STEP, pid, (void *)1, sig); break; case LINUX_PTRACE_GETREGS: error = linux_ptrace_getregs(td, pid, (void *)uap->data); @@ -586,19 +595,21 @@ error = linux_ptrace_setregs(td, pid, (void *)uap->data); break; case LINUX_PTRACE_ATTACH: - error = kern_ptrace(td, PT_ATTACH, pid, addr, uap->data); + error = kern_ptrace(td, false, PT_ATTACH, pid, addr, + uap->data); break; case LINUX_PTRACE_DETACH: error = map_signum(uap->data, &sig); if (error != 0) break; - error = kern_ptrace(td, PT_DETACH, pid, (void *)1, sig); + error = kern_ptrace(td, false, PT_DETACH, pid, (void *)1, sig); break; case LINUX_PTRACE_SYSCALL: error = map_signum(uap->data, &sig); if (error != 0) break; - error = kern_ptrace(td, PT_SYSCALL, pid, (void *)1, sig); + error = kern_ptrace(td, false, PT_SYSCALL, pid, (void *)1, + sig); break; case LINUX_PTRACE_SETOPTIONS: error = linux_ptrace_setoptions(td, pid, uap->data); diff --git a/sys/i386/linux/linux_ptrace_machdep.c b/sys/i386/linux/linux_ptrace_machdep.c --- a/sys/i386/linux/linux_ptrace_machdep.c +++ b/sys/i386/linux/linux_ptrace_machdep.c @@ -259,13 +259,13 @@ case PTRACE_POKETEXT: case PTRACE_POKEDATA: case PTRACE_KILL: - error = kern_ptrace(td, req, pid, addr, uap->data); + error = kern_ptrace(td, false, req, pid, addr, uap->data); break; case PTRACE_PEEKTEXT: case PTRACE_PEEKDATA: { /* need to preserve return value */ int rval = td->td_retval[0]; - error = kern_ptrace(td, req, pid, addr, 0); + error = kern_ptrace(td, false, req, pid, addr, 0); if (error == 0) error = copyout(td->td_retval, (void *)uap->data, sizeof(l_int)); @@ -273,20 +273,20 @@ break; } case PTRACE_DETACH: - error = kern_ptrace(td, PT_DETACH, pid, (void *)1, + error = kern_ptrace(td, false, PT_DETACH, pid, (void *)1, map_signum(uap->data)); break; case PTRACE_SINGLESTEP: case PTRACE_CONT: - error = kern_ptrace(td, req, pid, (void *)1, + error = kern_ptrace(td, false, req, pid, (void *)1, map_signum(uap->data)); break; case PTRACE_ATTACH: - error = kern_ptrace(td, PT_ATTACH, pid, addr, uap->data); + error = kern_ptrace(td, false, PT_ATTACH, pid, addr, uap->data); break; case PTRACE_GETREGS: /* Linux is using data where FreeBSD is using addr */ - error = kern_ptrace(td, PT_GETREGS, pid, &u.bsd_reg, 0); + error = kern_ptrace(td, false, PT_GETREGS, pid, &u.bsd_reg, 0); if (error == 0) { map_regs_to_linux(&u.bsd_reg, &r.reg); error = copyout(&r.reg, (void *)uap->data, @@ -303,7 +303,8 @@ break; case PTRACE_GETFPREGS: /* Linux is using data where FreeBSD is using addr */ - error = kern_ptrace(td, PT_GETFPREGS, pid, &u.bsd_fpreg, 0); + error = kern_ptrace(td, false, PT_GETFPREGS, pid, + &u.bsd_fpreg, 0); if (error == 0) { map_fpregs_to_linux(&u.bsd_fpreg, &r.fpreg); error = copyout(&r.fpreg, (void *)uap->data, @@ -315,7 +316,7 @@ error = copyin((void *)uap->data, &r.fpreg, sizeof(r.fpreg)); if (error == 0) { map_fpregs_from_linux(&u.bsd_fpreg, &r.fpreg); - error = kern_ptrace(td, PT_SETFPREGS, pid, + error = kern_ptrace(td, false, PT_SETFPREGS, pid, &u.bsd_fpreg, 0); } break; @@ -409,7 +410,8 @@ * as necessary. */ if (uap->addr < sizeof(struct linux_pt_reg)) { - error = kern_ptrace(td, PT_GETREGS, pid, &u.bsd_reg, 0); + error = kern_ptrace(td, false, PT_GETREGS, pid, + &u.bsd_reg, 0); if (error != 0) break; @@ -424,7 +426,8 @@ (l_int)uap->data; map_regs_from_linux(&u.bsd_reg, &r.reg); - error = kern_ptrace(td, PT_SETREGS, pid, &u.bsd_reg, 0); + error = kern_ptrace(td, false; PT_SETREGS, pid, + &u.bsd_reg, 0); } /* @@ -432,8 +435,8 @@ */ if (uap->addr >= LINUX_DBREG_OFFSET && uap->addr <= LINUX_DBREG_OFFSET + LINUX_DBREG_SIZE) { - error = kern_ptrace(td, PT_GETDBREGS, pid, &u.bsd_dbreg, - 0); + error = kern_ptrace(td, false, PT_GETDBREGS, pid, + &u.bsd_dbreg, 0); if (error != 0) break; @@ -447,7 +450,7 @@ *(l_int *)((char *)&u.bsd_dbreg + uap->addr) = uap->data; - error = kern_ptrace(td, PT_SETDBREGS, pid, + error = kern_ptrace(td, false, PT_SETDBREGS, pid, &u.bsd_dbreg, 0); } 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 @@ -92,7 +92,7 @@ { .sy_narg = AS(setuid_args), .sy_call = (sy_call_t *)sys_setuid, .sy_auevent = AUE_SETUID, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 23 = setuid */ { .sy_narg = 0, .sy_call = (sy_call_t *)sys_getuid, .sy_auevent = AUE_GETUID, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 24 = getuid */ { .sy_narg = 0, .sy_call = (sy_call_t *)sys_geteuid, .sy_auevent = AUE_GETEUID, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 25 = geteuid */ - { .sy_narg = AS(ptrace_args), .sy_call = (sy_call_t *)sys_ptrace, .sy_auevent = AUE_PTRACE, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 26 = ptrace */ + { .sy_narg = AS(ptrace_args), .sy_call = (sy_call_t *)sys_ptrace, .sy_auevent = AUE_PTRACE, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 26 = ptrace */ { .sy_narg = AS(recvmsg_args), .sy_call = (sy_call_t *)sys_recvmsg, .sy_auevent = AUE_RECVMSG, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 27 = recvmsg */ { .sy_narg = AS(sendmsg_args), .sy_call = (sy_call_t *)sys_sendmsg, .sy_auevent = AUE_SENDMSG, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 28 = sendmsg */ { .sy_narg = AS(recvfrom_args), .sy_call = (sy_call_t *)sys_recvfrom, .sy_auevent = AUE_RECVFROM, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 29 = recvfrom */ @@ -669,6 +669,6 @@ { .sy_narg = AS(pdrfork_args), .sy_call = (sy_call_t *)sys_pdrfork, .sy_auevent = AUE_PDRFORK, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 600 = pdrfork */ { .sy_narg = AS(pdwait_args), .sy_call = (sy_call_t *)sys_pdwait, .sy_auevent = AUE_PDWAIT, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 601 = pdwait */ { .sy_narg = AS(renameat2_args), .sy_call = (sy_call_t *)sys_renameat2, .sy_auevent = AUE_RENAMEAT, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 602 = renameat2 */ - { .sy_narg = AS(pdopenpid_args), .sy_call = (sy_call_t *)sys_pdopenpid, .sy_auevent = AUE_PDOPENPID, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 603 = pdopenpid */ + { .sy_narg = AS(pdopenpid_args), .sy_call = (sy_call_t *)sys_pdopenpid, .sy_auevent = AUE_PDOPENPID, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 603 = pdopenpid */ { .sy_narg = AS(pddupfd_args), .sy_call = (sy_call_t *)sys_pddupfd, .sy_auevent = AUE_NULL, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 604 = pddupfd */ }; diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c --- a/sys/kern/kern_descrip.c +++ b/sys/kern/kern_descrip.c @@ -1906,7 +1906,7 @@ /* * Fill the given filecaps structure with full rights. */ -static void +void filecaps_fill(struct filecaps *fcaps) { diff --git a/sys/kern/kern_exit.c b/sys/kern/kern_exit.c --- a/sys/kern/kern_exit.c +++ b/sys/kern/kern_exit.c @@ -720,6 +720,9 @@ } } else PROC_LOCK(p->p_pptr); + nq = proc_realparent(p); + if (p->p_pptr != nq) + wakeup(nq); sx_xunlock(&proctree_lock); if (signal_parent == 1) { @@ -982,13 +985,16 @@ * lock as part of its work. */ void -proc_reap(struct thread *td, struct proc *p, int *status, int options) +proc_reap(struct thread *td, struct proc *p, int *status, int options, + int zombieref) { struct proc *q, *t; sx_assert(&proctree_lock, SA_XLOCKED); PROC_LOCK_ASSERT(p, MA_OWNED); KASSERT(p->p_state == PRS_ZOMBIE, ("proc_reap: !PRS_ZOMBIE")); + MPASS(zombieref == P2_ZOMBIEREF_PARENT || + zombieref == P2_ZOMBIEREF_PROCDESC); mtx_spin_wait_unlocked(&p->p_slock); @@ -1006,6 +1012,14 @@ return; } + p->p_flag2 &= ~zombieref; + if ((p->p_flag2 & (P2_ZOMBIEREF_PROCDESC | P2_ZOMBIEREF_PARENT)) != + 0) { + PROC_UNLOCK(p); + sx_xunlock(&proctree_lock); + return; + } + PROC_LOCK(q); sigqueue_take(p->p_ksi); PROC_UNLOCK(q); @@ -1187,7 +1201,7 @@ switch (idtype) { case P_ALL: - if (p->p_procdesc == NULL || + if ((p->p_flag2 & P2_ZOMBIEREF_PARENT) != 0 || (p->p_pptr == td->td_proc && (p->p_flag & P_TRACED) != 0)) { break; @@ -1241,14 +1255,19 @@ return (0); } - if (p_canwait(td, p)) { + if (p_canwait(td, p) != 0 || + ((options & WEXITED) == 0 && p->p_state == PRS_ZOMBIE) || + (p->p_flag2 & P2_ZOMBIEREF_PARENT) == 0) { PROC_UNLOCK(p); return (0); } - if ((options & WEXITED) == 0 && p->p_state == PRS_ZOMBIE) { - PROC_UNLOCK(p); - return (0); + if (check_only) { + if ((p->p_flag2 & P2_ORPHAN_REPORTED) != 0) { + PROC_UNLOCK(p); + return (0); + } + p->p_flag2 |= P2_ORPHAN_REPORTED; } /* @@ -1275,7 +1294,7 @@ wait_fill_wrusage(p, wrusage); if (p->p_state == PRS_ZOMBIE && !check_only) { - proc_reap(td, p, status, options); + proc_reap(td, p, status, options, P2_ZOMBIEREF_PARENT); return (-1); } return (1); @@ -1519,8 +1538,9 @@ KASSERT(ret != -1, ("reaped an orphan (pid %d)", (int)td->td_retval[0])); PROC_UNLOCK(p); - nfound++; - break; + td->td_retval[0] = p->p_pid; + sx_xunlock(&proctree_lock); + return (0); } } } @@ -1583,7 +1603,8 @@ error = p_canwait(td, p); if (error != 0) break; - if ((options & WEXITED) == 0 && p->p_state == PRS_ZOMBIE) { + if (((options & WEXITED) == 0 && p->p_state == PRS_ZOMBIE) || + (p->p_flag2 & P2_ZOMBIEREF_PROCDESC) == 0) { error = ESRCH; break; } @@ -1592,7 +1613,7 @@ wait_fill_wrusage(p, wrusage); if (p->p_state == PRS_ZOMBIE) { - proc_reap(td, p, status, options); + proc_reap(td, p, status, options, P2_ZOMBIEREF_PROCDESC); goto exit_unlocked; } @@ -1665,6 +1686,14 @@ child->p_pptr = parent; if (set_oppid) child->p_oppid = parent->p_pid; + + /* + * When reparenting the child to the reaper, re-enable + * waitpid(2) for it, so that the zombie can be collected. + */ + if ((child->p_flag & P_TRACED) == 0 && child->p_reaper == parent && + (child->p_flag2 & P2_ZOMBIEREF_PARENT) == 0) + child->p_flag2 |= P2_ZOMBIEREF_PARENT; } static void 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 @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -119,6 +120,7 @@ sys_pdfork(struct thread *td, struct pdfork_args *uap) { struct fork_req fr; + struct filecaps fcaps; int error, fd, pid; bzero(&fr, sizeof(fr)); @@ -126,6 +128,10 @@ fr.fr_pidp = &pid; fr.fr_pd_fd = &fd; fr.fr_pd_flags = uap->flags; + filecaps_fill(&fcaps); + if ((uap->flags & PD_PTRACE_CAP) == 0) + cap_rights_clear(&fcaps.fc_rights, CAP_PTRACE); + fr.fr_pd_fcaps = &fcaps; AUDIT_ARG_FFLAGS(uap->flags); /* * It is necessary to return fd by reference because 0 is a valid file @@ -194,6 +200,7 @@ sys_pdrfork(struct thread *td, struct pdrfork_args *uap) { struct fork_req fr; + struct filecaps fcaps; int error, fd, pid; bzero(&fr, sizeof(fr)); @@ -226,6 +233,10 @@ fr.fr_pidp = &pid; fr.fr_pd_fd = &fd; fr.fr_pd_flags = uap->pdflags; + filecaps_fill(&fcaps); + if ((uap->pdflags & PD_PTRACE_CAP) == 0) + cap_rights_clear(&fcaps.fc_rights, CAP_PTRACE); + fr.fr_pd_fcaps = &fcaps; error = fork1(td, &fr); if (error == 0) { td->td_retval[0] = pid; @@ -547,6 +558,8 @@ P2_STKGAP_DISABLE | P2_STKGAP_DISABLE_EXEC | P2_NO_NEW_PRIVS | P2_WXORX_DISABLE | P2_WXORX_ENABLE_EXEC | P2_LOGSIGEXIT_CTL | P2_LOGSIGEXIT_ENABLE); + p2->p_flag2 |= (fr->fr_flags & RFPROCDESC) != 0 ? + P2_ZOMBIEREF_PROCDESC : P2_ZOMBIEREF_PARENT; p2->p_swtick = ticks; if (p1->p_flag & P_PROFIL) startprofclock(p2); @@ -1050,8 +1063,10 @@ if (flags & RFPROCDESC) { error = procdesc_falloc(td, &fp_procdesc, fr->fr_pd_fd, fr->fr_pd_flags, fr->fr_pd_fcaps); - if (error != 0) + if (error != 0) { + filecaps_free(fr->fr_pd_fcaps); goto fail2; + } AUDIT_ARG_FD(*fr->fr_pd_fd); } diff --git a/sys/kern/kern_proc.c b/sys/kern/kern_proc.c --- a/sys/kern/kern_proc.c +++ b/sys/kern/kern_proc.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -2399,12 +2400,19 @@ return (EINVAL); name = (int *)arg1; + sx_slock(&proctree_lock); error = pget((pid_t)name[0], PGET_CANSEE, &p); if (error != 0) - return (error); - sv_name = p->p_sysent->sv_name; + goto out; + error = p_canopen(curthread, p); + if (error == 0) + sv_name = p->p_sysent->sv_name; PROC_UNLOCK(p); - return (sysctl_handle_string(oidp, sv_name, 0, req)); +out: + sx_sunlock(&proctree_lock); + if (error == 0) + error = sysctl_handle_string(oidp, sv_name, 0, req); + return (error); } #ifdef KINFO_OVMENTRY_SIZE @@ -3430,7 +3438,7 @@ CTLFLAG_MPSAFE, sysctl_kern_proc_pathname, "Process executable path"); static SYSCTL_NODE(_kern_proc, KERN_PROC_SV_NAME, sv_name, CTLFLAG_RD | - CTLFLAG_MPSAFE, sysctl_kern_proc_sv_name, + CTLFLAG_MPSAFE | CTLFLAG_CAPRD, sysctl_kern_proc_sv_name, "Process syscall vector name (ABI type)"); static SYSCTL_NODE(_kern_proc, (KERN_PROC_GID | KERN_PROC_INC_THREAD), gid_td, diff --git a/sys/kern/kern_prot.c b/sys/kern/kern_prot.c --- a/sys/kern/kern_prot.c +++ b/sys/kern/kern_prot.c @@ -46,14 +46,13 @@ * System calls related to processes and protection */ -#include #include "opt_inet.h" #include "opt_inet6.h" -#include #include #include #include +#include #include #include #include @@ -2309,6 +2308,24 @@ return (uidsubset && grpsubset); } +int +p_canopen(struct thread *td, struct proc *p) +{ +#ifdef INVARIANTS + if (IN_CAPABILITY_MODE(td)) + sx_assert(&proctree_lock, SX_LOCKED); +#endif + + /* + * Allow implicit parent in cap mode: either real parent or + * debugger can open pid. + */ + if (!IN_CAPABILITY_MODE(td) || (allow_ptrace_in_cap_mode && + (td->td_proc == p->p_pptr || p->p_oppid == td->td_proc->p_pid))) + return (0); + return (ECAPMODE); +} + /*- * Determine whether td may debug p. * Returns: 0 for permitted, an errno value otherwise 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_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); const cap_rights_t cap_recv_rights = CAP_RIGHTS_INITIALIZER(CAP_RECV); 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 @@ -307,6 +307,7 @@ PROCDESC_UNLOCK(pd); pd->pd_proc = NULL; p->p_procdesc = NULL; + p->p_flag2 &= ~P2_ZOMBIEREF_PROCDESC; procdesc_free(pd); return (true); } @@ -316,7 +317,7 @@ /* Wakeup all waiters for this procdesc' process exit. */ wakeup(&p->p_procdesc); - return (false); + return ((p->p_flag2 & P2_ZOMBIEREF_PARENT) != 0); } void @@ -418,7 +419,7 @@ * process's reference to the process descriptor when it * calls back into procdesc_reap(). */ - proc_reap(curthread, p, NULL, 0); + proc_reap(curthread, p, NULL, 0, P2_ZOMBIEREF_PROCDESC); } else if (pd->pd_fpcount == 0) /* last procdesc */ { /* * If the process is not yet dead, we need to kill it, @@ -429,11 +430,16 @@ */ pd->pd_proc = NULL; p->p_procdesc = NULL; + p->p_flag2 &= ~P2_ZOMBIEREF_PROCDESC; pd->pd_pid = -1; procdesc_free(pd); - /* Failed finstall() should not cause reaping. */ - if ((fp->f_pdflags & F_PD_NOFINSTALL) == 0) { + /* + * A reference for waitpid() or failed + * finstall() should not cause reaping. + */ + if ((fp->f_pdflags & F_PD_NOFINSTALL) == 0 || + (p->p_flag2 & P2_ZOMBIEREF_PARENT) == 0) { /* * Next, reparent it to its reaper * (usually init(8)) so that there's @@ -493,16 +499,25 @@ procdesc_kqops_event(struct knote *kn, long hint) { struct procdesc *pd; + struct proc *p; u_int event; pd = kn->kn_fp->f_data; if (hint == 0) { /* * Initial test after registration. Generate notes in - * case the process already terminated before registration. + * case the process already terminated before + * registration or is stopped or traced with an event + * pending. */ - event = (pd->pd_flags & PDF_EXITED) != 0 ? (NOTE_EXIT | - NOTE_PDSIGCHLD) : 0; + p = pd->pd_proc; + if ((pd->pd_flags & PDF_EXITED) != 0) + event = NOTE_EXIT | NOTE_PDSIGCHLD; + else if ((atomic_load_int(&p->p_flag) & (P_STOPPED_SIG | + P_STOPPED_TRACE)) != 0) + event = NOTE_PDSIGCHLD; + else + event = 0; } else { /* Mask off extra data. */ event = (u_int)hint & NOTE_PCTRLMASK; @@ -631,8 +646,15 @@ PROC_UNLOCK(p); return (EBUSY); } + error = p_canopen(td, p); + if (error != 0) { + PROC_UNLOCK(p); + return (error); + } + pd = p->p_procdesc; if (pd != NULL) { + MPASS((p->p_flag2 & P2_ZOMBIEREF_PROCDESC) != 0); refcount_acquire(&pd->pd_refcount); PROCDESC_LOCK(pd); MPASS(pd->pd_fpcount > 0); @@ -644,6 +666,8 @@ pd->pd_proc = p; pd->pd_pid = p->p_pid; p->p_procdesc = pd; + MPASS((p->p_flag2 & P2_ZOMBIEREF_PROCDESC) == 0); + p->p_flag2 |= P2_ZOMBIEREF_PROCDESC; } procdesc_finit(pd, fp); PROC_UNLOCK(p); @@ -655,6 +679,7 @@ { struct file *fp; struct procdesc *pdf; + struct filecaps fcaps; int error, fd, fflags; error = falloc_noinstall(td, &fp); @@ -664,13 +689,16 @@ pdf = procdesc_alloc(flags); if ((flags & PD_DAEMON) != 0) fp->f_pdflags |= F_PD_NOKILL; + filecaps_fill(&fcaps); + if ((flags & PD_PTRACE_CAP) == 0) + cap_rights_clear(&fcaps.fc_rights, CAP_PTRACE); sx_xlock(&proctree_lock); error = pdopenpid1(td, pid, &pdf, fp); sx_xunlock(&proctree_lock); if (error == 0) { - error = finstall(td, fp, &fd, fflags, NULL); + error = finstall(td, fp, &fd, fflags, &fcaps); if (error == 0) { td->td_retval[0] = fd; } else { @@ -679,6 +707,7 @@ * return file descriptor to userspace. */ fp->f_pdflags |= F_PD_NOKILL | F_PD_NOFINSTALL; + filecaps_free(&fcaps); } } fdrop(fp, td); diff --git a/sys/kern/sys_process.c b/sys/kern/sys_process.c --- a/sys/kern/sys_process.c +++ b/sys/kern/sys_process.c @@ -32,7 +32,7 @@ */ #include -#include +#include #include #include #include @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -733,10 +735,12 @@ syscallarg_t args[nitems(td->td_sa.args)]; struct ptrace_sc_ret psr; int ptevents; + struct ptrace_child *children; } r; syscallarg_t pscr_args[nitems(td->td_sa.args)]; void *addr; int error; + bool pd_mode; if (!allow_ptrace) return (ENOSYS); @@ -746,6 +750,9 @@ AUDIT_ARG_CMD(uap->req); AUDIT_ARG_VALUE(uap->data); addr = &r; + pd_mode = (uap->req & PT_PROCDESC) != 0; + uap->req &= ~PT_PROCDESC; + switch (uap->req) { case PT_GET_EVENT_MASK: case PT_LWPINFO: @@ -816,6 +823,14 @@ break; r.sr.pscr_args = pscr_args; break; + case PT_GET_CHILDREN: + if (uap->addr == NULL) + addr = NULL; + else if (uap->data < 0) + error = EINVAL; + else + addr = &r.children; + break; case PTINTERNAL_FIRST ... PTINTERNAL_LAST: error = EINVAL; break; @@ -826,7 +841,7 @@ if (error != 0) return (error); - error = kern_ptrace(td, uap->req, uap->pid, addr, uap->data); + error = kern_ptrace(td, pd_mode, uap->req, uap->pid, addr, uap->data); if (error != 0) return (error); @@ -870,6 +885,13 @@ offsetof(struct ptrace_sc_remote, pscr_ret), sizeof(r.sr.pscr_ret)); break; + case PT_GET_CHILDREN: + if (uap->addr != NULL) { + error = copyout(r.children, uap->addr, + td->td_retval[0] * sizeof(struct ptrace_child)); + free(r.children, M_TEMP); + } + break; } return (error); @@ -965,6 +987,56 @@ return (0); } +static int +ptrace_count_children(struct thread *td, struct proc *p) +{ + struct proc *pp; + int error, num; + + sx_assert(&proctree_lock, SX_LOCKED); + num = 0; + LIST_FOREACH(pp, &p->p_children, p_sibling) { + PROC_LOCK(pp); + error = p_cansee(td, pp); + PROC_UNLOCK(pp); + if (error != 0) + continue; + num++; + } + LIST_FOREACH(pp, &p->p_orphans, p_orphan) { + PROC_LOCK(pp); + error = p_cansee(td, pp); + PROC_UNLOCK(pp); + if (error != 0) + continue; + num++; + } + return (num); +} + +static bool +ptrace_report_child(struct thread *td, struct proc *p, struct proc *pp, + struct ptrace_child *ptc) +{ + sx_assert(&proctree_lock, SX_LOCKED); + + PROC_LOCK(pp); + if (p_cansee(td, pp) != 0) { + PROC_UNLOCK(pp); + return (false); + } + ptc->pid = pp->p_pid; + if ((pp->p_flag & P_TRACED) != 0) { + ptc->flags |= PTCHLD_TRACED; + if (pp->p_pptr == td->td_proc) + ptc->flags |= PTCHLD_TRACED_BY_ME; + } + if ((pp->p_flag & P_WEXIT) != 0) + ptc->flags |= PTCHLD_EXITED; + PROC_UNLOCK(pp); + return (true); +} + static struct thread * ptrace_sel_coredump_thread(struct proc *p) { @@ -980,8 +1052,14 @@ return (NULL); } +bool allow_ptrace_in_cap_mode = true; +SYSCTL_BOOL(_security_bsd, OID_AUTO, allow_ptrace_in_cap_mode, CTLFLAG_RWTUN, + &allow_ptrace_in_cap_mode, 0, + "Allow ptrace(2) in capability mode"); + int -kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data) +kern_ptrace(struct thread *td, bool pd_mode, int req, int pid, void *addr, + int data) { struct iovec iov; struct uio uio; @@ -995,7 +1073,9 @@ struct ptrace_coredump *pc; struct thr_coredump_req *tcq; struct thr_syscall_req *tsr; - int error, num, tmp; + struct file *pfp; + struct ptrace_child *children, *ptc; + int error, num, num1, tmp; lwpid_t tid = 0, *buf; #ifdef COMPAT_FREEBSD32 int wrap32 = 0, safe = 0; @@ -1005,6 +1085,11 @@ curp = td->td_proc; proctree_locked = false; p2_req_set = false; + pfp = NULL; + + if (IN_CAPABILITY_MODE(td) && (!allow_ptrace_in_cap_mode || + (req == PT_ATTACH && !pd_mode))) + return (ECAPMODE); /* Lock proctree before locking the process. */ switch (req) { @@ -1021,6 +1106,7 @@ case PT_SET_EVENT_MASK: case PT_DETACH: case PT_GET_SC_ARGS: + case PT_GET_CHILDREN: sx_xlock(&proctree_lock); proctree_locked = true; break; @@ -1032,7 +1118,17 @@ p = td->td_proc; PROC_LOCK(p); } else { - if (pid <= PID_MAX) { + if (pd_mode) { + if (!proctree_locked) + sx_slock(&proctree_lock); + error = fget_procdesc(td, pid, &cap_ptrace_rights, + &pfp, NULL, &p); + if (!proctree_locked) + sx_sunlock(&proctree_lock); + if (error != 0) + goto fail_proctree; + tid = pid = p->p_pid; + } else if (pid <= PID_MAX) { if ((p = pfind(pid)) == NULL) { if (proctree_locked) sx_xunlock(&proctree_lock); @@ -1132,8 +1228,14 @@ /* Allow thread to clear single step for itself */ if (td->td_tid == tid) break; + goto default_check; - /* FALLTHROUGH */ + case PT_GET_CHILDREN: + if (p == curp) + break; + goto default_check; + +default_check: default: /* * Check for ptrace eligibility before waiting for @@ -1864,6 +1966,50 @@ free(tsr, M_TEMP); break; + case PT_GET_CHILDREN: + PROC_UNLOCK(p); +get_children_repeat: + num = ptrace_count_children(td, p); + if (addr == NULL) { + td->td_retval[0] = num; + PROC_LOCK(p); + break; + } + if (data < num * sizeof(struct ptrace_child)) { + error = ENOMEM; + PROC_LOCK(p); + break; + } + sx_xunlock(&proctree_lock); + children = mallocarray(num, sizeof(struct ptrace_child), + M_TEMP, M_WAITOK | M_ZERO); + sx_xlock(&proctree_lock); + num1 = ptrace_count_children(td, p); + if (num1 > num) { + free(children, M_TEMP); + goto get_children_repeat; + } + num = num1; + num1 = 0; + td->td_retval[0] = num; + LIST_FOREACH(pp, &p->p_children, p_sibling) { + MPASS(num1 < num); + ptc = &children[num1]; + if (ptrace_report_child(td, p, pp, ptc)) + num1++; + } + LIST_FOREACH(pp, &p->p_orphans, p_orphan) { + MPASS(num1 < num); + ptc = &children[num1]; + if (ptrace_report_child(td, p, pp, ptc)) { + num1++; + ptc->flags |= PTCHLD_ORPHAN; + } + } + *(struct ptrace_child **)addr = children; + PROC_LOCK(p); + break; + default: #ifdef __HAVE_PTRACE_MACHDEP if (req >= PT_FIRSTMACH) { @@ -1886,8 +2032,11 @@ p->p_flag2 &= ~P2_PTRACEREQ; } PROC_UNLOCK(p); +fail_proctree: if (proctree_locked) sx_xunlock(&proctree_lock); + if (pfp != NULL) + fdrop(pfp, td); return (error); } #undef PROC_READ diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master --- a/sys/kern/syscalls.master +++ b/sys/kern/syscalls.master @@ -264,7 +264,7 @@ 25 AUE_GETEUID STD|CAPENABLED { uid_t geteuid(void); } -26 AUE_PTRACE STD { +26 AUE_PTRACE STD|CAPENABLED { int ptrace( int req, pid_t pid, @@ -3429,7 +3429,7 @@ int flags ); } -603 AUE_PDOPENPID STD { +603 AUE_PDOPENPID STD|CAPENABLED { int pdopenpid( pid_t pid, int flags 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_ptrace_rights; extern const cap_rights_t cap_pwrite_rights; extern const cap_rights_t cap_read_rights; extern const cap_rights_t cap_recv_rights; diff --git a/sys/sys/capsicum.h b/sys/sys/capsicum.h --- a/sys/sys/capsicum.h +++ b/sys/sys/capsicum.h @@ -299,7 +299,9 @@ /* Allows pddupfd(2). */ #define CAP_PDDUPFD CAPRIGHT(1, 0x0000000000800000ULL) -#define CAP_UNUSED1_25 CAPRIGHT(1, 0x0000000001000000ULL) +/* Allows ptrace(PT_PROCDESC) */ +#define CAP_PTRACE CAPRIGHT(1, 0x0000000001000000ULL) + #define CAP_UNUSED1_26 CAPRIGHT(1, 0x0000000002000000ULL) #define CAP_UNUSED1_27 CAPRIGHT(1, 0x0000000004000000ULL) #define CAP_UNUSED1_28 CAPRIGHT(1, 0x0000000008000000ULL) @@ -333,8 +335,8 @@ #define CAP_UNUSED1_56 CAPRIGHT(1, 0x0080000000000000ULL) #define CAP_UNUSED1_57 CAPRIGHT(1, 0x0100000000000000ULL) -/* All used bits for index 1. */ -#define CAP_ALL1 CAPRIGHT(1, 0x0000000000FFFFFFULL) +/* All default bits for index 1. */ +#define CAP_ALL1 CAPRIGHT(1, 0x0000000001FFFFFFULL) /* Backward compatibility. */ #define CAP_POLL_EVENT CAP_EVENT diff --git a/sys/sys/filedesc.h b/sys/sys/filedesc.h --- a/sys/sys/filedesc.h +++ b/sys/sys/filedesc.h @@ -243,6 +243,7 @@ bool filecaps_copy(const struct filecaps *src, struct filecaps *dst, bool locked); void filecaps_move(struct filecaps *src, struct filecaps *dst); +void filecaps_fill(struct filecaps *fcaps); void filecaps_free(struct filecaps *fcaps); int closef(struct file *fp, struct thread *td); diff --git a/sys/sys/proc.h b/sys/sys/proc.h --- a/sys/sys/proc.h +++ b/sys/sys/proc.h @@ -903,6 +903,9 @@ #define P2_LOGSIGEXIT_ENABLE 0x00800000 /* Disable logging on sigexit */ #define P2_LOGSIGEXIT_CTL 0x01000000 /* Override kern.logsigexit */ #define P2_HWT 0x02000000 /* Process is using HWT. */ +#define P2_ZOMBIEREF_PARENT 0x04000000 +#define P2_ZOMBIEREF_PROCDESC 0x08000000 +#define P2_ORPHAN_REPORTED 0x10000000 /* Flags protected by proctree_lock, kept in p_treeflags. */ #define P_TREE_ORPHANED 0x00000001 /* Reparented, on orphan list */ @@ -1186,6 +1189,7 @@ void maybe_yield(void); void mi_switch(int flags); int p_candebug(struct thread *td, struct proc *p); +int p_canopen(struct thread *td, struct proc *p); int p_cansee(struct thread *td, struct proc *p); int p_cansched(struct thread *td, struct proc *p); int p_cansignal(struct thread *td, struct proc *p, int signum); @@ -1205,7 +1209,8 @@ void proc_linkup0(struct proc *p, struct thread *td); void proc_linkup(struct proc *p, struct thread *td); struct proc *proc_realparent(struct proc *child); -void proc_reap(struct thread *td, struct proc *p, int *status, int options); +void proc_reap(struct thread *td, struct proc *p, int *status, int options, + int zombieref); void proc_reparent(struct proc *child, struct proc *newparent, bool set_oppid); void proc_set_p2_wexit(struct proc *p); void proc_set_traced(struct proc *p, bool stop); diff --git a/sys/sys/procdesc.h b/sys/sys/procdesc.h --- a/sys/sys/procdesc.h +++ b/sys/sys/procdesc.h @@ -153,7 +153,8 @@ */ #define PD_DAEMON 0x00000001 /* Don't exit when procdesc closes. */ #define PD_CLOEXEC 0x00000002 /* Close file descriptor on exec. */ +#define PD_PTRACE_CAP 0x00000004 /* Allow PT_PROCDESC in cap mode. */ -#define PD_ALLOWED_AT_FORK (PD_DAEMON | PD_CLOEXEC) +#define PD_ALLOWED_AT_FORK (PD_DAEMON | PD_CLOEXEC | PD_PTRACE_CAP) #endif /* !_SYS_PROCDESC_H_ */ diff --git a/sys/sys/ptrace.h b/sys/sys/ptrace.h --- a/sys/sys/ptrace.h +++ b/sys/sys/ptrace.h @@ -36,6 +36,8 @@ #include #include +#define PT_PROCDESC 0x80000000 /* pid is procdesc */ + #define PT_TRACE_ME 0 /* child declares it's being traced */ #define PT_READ_I 1 /* read word in child's I space */ #define PT_READ_D 2 /* read word in child's D space */ @@ -86,6 +88,7 @@ #define PT_SETREGSET 43 /* Set a target register set */ #define PT_SC_REMOTE 44 /* Execute a syscall */ #define PT_SET_SC_RET 45 /* Set (fake) syscall results */ +#define PT_GET_CHILDREN 46 /* Report children */ #define PT_FIRSTMACH 64 /* for machine-specific requests */ #define PT_LASTMACH 127 @@ -206,6 +209,17 @@ syscallarg_t *pscr_args; }; +#define PTCHLD_TRACED 0x00000001 +#define PTCHLD_TRACED_BY_ME 0x00000002 +#define PTCHLD_EXITED 0x00000004 +#define PTCHLD_ORPHAN 0x00000008 + +struct ptrace_child { + pid_t pid; + int flags; + uint64ptr_t rsrv[3]; +}; + #ifdef _KERNEL #include @@ -281,6 +295,7 @@ void ptrace_unsuspend(struct proc *p); extern bool allow_ptrace; +extern bool allow_ptrace_in_cap_mode; #else /* !_KERNEL */ diff --git a/sys/sys/syscallsubr.h b/sys/sys/syscallsubr.h --- a/sys/sys/syscallsubr.h +++ b/sys/sys/syscallsubr.h @@ -308,8 +308,8 @@ int kern_preadv(struct thread *td, int fd, struct uio *auio, off_t offset); int kern_pselect(struct thread *td, int nd, fd_set *in, fd_set *ou, fd_set *ex, struct timeval *tvp, sigset_t *uset, int abi_nfdbits); -int kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, - int data); +int kern_ptrace(struct thread *td, bool pd_mode, int req, pid_t pid, + void *addr, int data); int kern_pwrite(struct thread *td, int fd, const void *buf, size_t nbyte, off_t offset); int kern_pwritev(struct thread *td, int fd, struct uio *auio, off_t offset); diff --git a/usr.bin/truss/main.c b/usr.bin/truss/main.c --- a/usr.bin/truss/main.c +++ b/usr.bin/truss/main.c @@ -31,13 +31,14 @@ * SUCH DAMAGE. */ -#include /* * The main module for truss. Surprisingly simple, but, then, the other * files handle the bulk of the work. And, of course, the kernel has to * do a lot of the work :). */ +#include +#include #include #include @@ -57,8 +58,8 @@ usage(void) { fprintf(stderr, "%s\n%s\n", - "usage: truss [-cfaedDHS] [-o file] [-s strsize] -p pid", - " truss [-cfaedDHS] [-o file] [-s strsize] command [args]"); + "usage: truss [-cfaedyYDHS] [-o file] [-s strsize] -p pid", + " truss [-cfaedyYDHS] [-o file] [-s strsize] command [args]"); exit(1); } @@ -85,7 +86,7 @@ trussinfo->strsize = 32; trussinfo->curthread = NULL; LIST_INIT(&trussinfo->proclist); - while ((c = getopt(ac, av, "p:o:facedDs:SH")) != -1) { + while ((c = getopt(ac, av, "p:o:facedyYDs:SH")) != -1) { switch (c) { case 'p': /* specified pid */ pid = atoi(optarg); @@ -121,6 +122,13 @@ if (errstr) errx(1, "maximum string size is %s: %s", errstr, optarg); break; + case 'y': + trussinfo->cap_mode = true; + break; + case 'Y': + trussinfo->cap_mode = true; + trussinfo->force_cap_mode = true; + break; case 'S': /* Don't trace signals */ trussinfo->flags |= NOSIGS; break; @@ -146,6 +154,12 @@ err(1, "cannot open %s", fname); } + if (trussinfo->cap_mode) { + trussinfo->pdkq = kqueue(); + if (trussinfo->pdkq == -1) + err(1, "kqueue"); + } + /* * If truss starts the process itself, it will ignore some signals -- * they should be passed off to the process, which may or may not diff --git a/usr.bin/truss/setup.c b/usr.bin/truss/setup.c --- a/usr.bin/truss/setup.c +++ b/usr.bin/truss/setup.c @@ -31,13 +31,15 @@ * SUCH DAMAGE. */ -#include /* * Various setup functions for truss. Not the cleanest-written code, * I'm afraid. */ +#include +#include #include +#include #include #include #include @@ -59,6 +61,8 @@ #include "syscall.h" #include "extern.h" +#define WFLAGS (WTRAPPED | WEXITED | WCONTINUED | WUNTRACED) + struct procabi_table { const char *name; struct procabi *abi; @@ -68,8 +72,9 @@ static void enter_syscall(struct trussinfo *, struct threadinfo *, struct ptrace_lwpinfo *); -static void new_proc(struct trussinfo *, pid_t, lwpid_t); - +static bool new_proc(struct trussinfo *, int, pid_t, lwpid_t, bool, bool); +static void new_proc_register_kev(struct trussinfo *info, int pfd); +static struct procinfo *find_proc(struct trussinfo *info, pid_t pid); static struct procabi freebsd = { .type = "FreeBSD", @@ -138,6 +143,59 @@ #endif }; +#if 0 +static int +t_ptrace(struct trussinfo *info, int req, int pid, caddr_t addr, int data) +{ + return (ptrace(req | (info->cap_mode ? PT_PROCDESC : 0), pid, + addr, data)); +} +#endif + +static int +truss_ptrace(struct trussinfo *info, int req, struct procinfo *p, caddr_t addr, + int data) +{ + if (info->cap_mode) + return (ptrace(req | PT_PROCDESC, p->pfd, addr, data)); + return (ptrace(req, p->pid, addr, data)); +} + +static int +t_wait(struct trussinfo *info, int pid, int *status, int wflags) +{ + if (info->cap_mode) + return (pdwait(pid, status, wflags, NULL, NULL)); + return (waitpid(pid, status, wflags)); +} + +static int +truss_wait(struct trussinfo *info, struct procinfo *p, int *status, + int wflags) +{ + if (info->cap_mode) + return (pdwait(p->pfd, status, wflags, NULL, NULL)); + return (waitpid(p->pid, status, wflags)); +} + +#if 0 +static int +t_kill(struct trussinfo *info, int pid, int sig) +{ + if (info->cap_mode) + return (pdkill(pid, sig)); + return (kill(pid, sig)); +} +#endif + +static int +truss_kill(struct trussinfo *info, struct procinfo *p, int sig) +{ + if (info->cap_mode) + return (pdkill(p->pfd, sig)); + return (kill(p->pid, sig)); +} + /* * setup_and_wait() is called to start a process. All it really does * is fork(), enable tracing in the child, and then exec the given @@ -148,21 +206,38 @@ setup_and_wait(struct trussinfo *info, char *command[]) { pid_t pid; + int fd, res; - pid = vfork(); - if (pid == -1) - err(1, "fork failed"); + if (info->cap_mode) { + pid = pdfork(&fd, PD_DAEMON | PD_CLOEXEC | PD_PTRACE_CAP); + if (pid == -1) + err(1, "fork failed"); + } else { + pid = vfork(); + fd = -1; + } if (pid == 0) { /* Child */ ptrace(PT_TRACE_ME, 0, 0, 0); execvp(command[0], command); err(1, "execvp %s", command[0]); } + if (info->cap_mode) { + if (cap_enter() == -1) { + if (info->force_cap_mode) + err(1, "cap_enter"); + else + warn("cap_enter"); + } + new_proc_register_kev(info, fd); + } + /* Only in the parent here */ - if (waitpid(pid, NULL, 0) < 0) + res = t_wait(info, info->cap_mode ? fd : pid, NULL, WFLAGS); + if (res < 0) err(1, "unexpected stop in waitpid"); - new_proc(info, pid, 0); + new_proc(info, pid, 0, fd, false, false); } /* @@ -171,20 +246,37 @@ void start_tracing(struct trussinfo *info, pid_t pid) { - int ret, retry; + int fd, ret, retry; + + if (info->cap_mode) { + fd = pdopenpid(pid, PD_DAEMON | PD_CLOEXEC | PD_PTRACE_CAP); + if (fd == -1) + err(1, "Cannot open the target process"); + if (cap_enter() == -1) { + if (info->force_cap_mode) + err(1, "cap_enter"); + else + warn("cap_enter"); + } + new_proc_register_kev(info, fd); + } else { + fd = -1; + } retry = 10; do { - ret = ptrace(PT_ATTACH, pid, NULL, 0); + ret = info->cap_mode ? ptrace(PT_ATTACH | PT_PROCDESC, fd, + NULL, 0) : ptrace(PT_ATTACH, pid, NULL, 0); usleep(200); } while (ret && retry-- > 0); if (ret) err(1, "Cannot attach to target process"); - if (waitpid(pid, NULL, 0) < 0) + ret = t_wait(info, info->cap_mode ? fd : pid, NULL, WFLAGS); + if (ret < 0) err(1, "Unexpected stop in waitpid"); - new_proc(info, pid, 0); + new_proc(info, pid, 0, fd, false, false); } /* @@ -201,31 +293,32 @@ } static void -detach_proc(pid_t pid) +detach_proc(struct trussinfo *info, struct procinfo *p) { - int sig, status; + int error, sig, status; /* * Stop the child so that we can detach. Filter out possible * lingering SIGTRAP events buffered in the threads. */ - kill(pid, SIGSTOP); + truss_kill(info, p, SIGSTOP); for (;;) { - if (waitpid(pid, &status, 0) < 0) + error = truss_wait(info, p, &status, WFLAGS); + if (error < 0) err(1, "Unexpected error in waitpid"); sig = WIFSTOPPED(status) ? WSTOPSIG(status) : 0; if (sig == SIGSTOP) break; if (sig == SIGTRAP) sig = 0; - if (ptrace(PT_CONTINUE, pid, (caddr_t)1, sig) < 0) + if (truss_ptrace(info, PT_CONTINUE, p, (caddr_t)1, sig) < 0) err(1, "Can not continue for detach"); } - if (ptrace(PT_DETACH, pid, (caddr_t)1, 0) < 0) + if (truss_ptrace(info, PT_DETACH, p, (caddr_t)1, 0) < 0) err(1, "Can not detach the process"); - kill(pid, SIGCONT); + truss_kill(info, p, SIGCONT); } /* @@ -297,12 +390,12 @@ lwpid_t *lwps; int i, nlwps; - nlwps = ptrace(PT_GETNUMLWPS, p->pid, NULL, 0); + nlwps = truss_ptrace(info, PT_GETNUMLWPS, p, NULL, 0); if (nlwps == -1) err(1, "Unable to fetch number of LWPs"); assert(nlwps > 0); lwps = calloc(nlwps, sizeof(*lwps)); - nlwps = ptrace(PT_GETLWPLIST, p->pid, (caddr_t)lwps, nlwps); + nlwps = truss_ptrace(info, PT_GETLWPLIST, p, (caddr_t)lwps, nlwps); if (nlwps == -1) err(1, "Unable to fetch LWP list"); for (i = 0; i < nlwps; i++) { @@ -318,27 +411,56 @@ } static void -new_proc(struct trussinfo *info, pid_t pid, lwpid_t lwpid) +new_proc_register_kev(struct trussinfo *info, int pfd) +{ + struct kevent ev[1]; + int error; + + if (!info->cap_mode) + return; + EV_SET(&ev[0], pfd, EVFILT_PROCDESC, EV_ADD, NOTE_EXIT | + NOTE_PDSIGCHLD | NOTE_FORK, 0, 0); + error = kevent(info->pdkq, ev, nitems(ev), NULL, 0, NULL); + if (error == -1) + err(1, "Unable to register pfd %d for notifications", pfd); +} + +static bool +new_proc(struct trussinfo *info, pid_t pid, lwpid_t lwpid, int pfd, + bool allow_known, bool wait_for) { struct procinfo *np; /* - * If this happens it means there is a bug in truss. Unfortunately - * this will kill any processes truss is attached to. + * If this happens it means there is a bug in truss. + * Unfortunately this will kill any processes truss is + * attached to. */ - LIST_FOREACH(np, &info->proclist, entries) { - if (np->pid == pid) + if (find_proc(info, pid) != NULL) { + if (allow_known) + return (false); + else errx(1, "Duplicate process for pid %ld", (long)pid); } + if (pfd == -1 && info->cap_mode) { + pfd = pdopenpid(pid, PD_DAEMON | PD_CLOEXEC | PD_PTRACE_CAP); + if (pfd == -1) + err(1, "pdopenid %d", pid); + if (wait_for) + t_wait(info, pfd, NULL, WFLAGS); + new_proc_register_kev(info, pfd); + } - if (info->flags & FOLLOWFORKS) - if (ptrace(PT_FOLLOW_FORK, pid, NULL, 1) == -1) - err(1, "Unable to follow forks for pid %ld", (long)pid); - if (ptrace(PT_LWP_EVENTS, pid, NULL, 1) == -1) - err(1, "Unable to enable LWP events for pid %ld", (long)pid); np = calloc(1, sizeof(struct procinfo)); np->pid = pid; + np->pfd = pfd; np->abi = find_abi(pid); + np->herald_printed = false; + if ((info->flags & FOLLOWFORKS) != 0 && truss_ptrace(info, + PT_FOLLOW_FORK, np, NULL, 1) == -1) + err(1, "Unable to follow forks for pid %ld", (long)pid); + if (truss_ptrace(info, PT_LWP_EVENTS, np, NULL, 1) == -1) + err(1, "Unable to enable LWP events for pid %ld", (long)pid); LIST_INIT(&np->threadlist); LIST_INIT(&np->fdlist); LIST_INSERT_HEAD(&info->proclist, np, entries); @@ -347,13 +469,21 @@ new_thread(np, lwpid); else add_threads(info, np); + return (true); } static void -free_proc(struct procinfo *p) +free_proc(struct trussinfo *info, struct procinfo *p) { struct threadinfo *t, *t2; struct fd_domain *f, *f2; + struct kevent ev[1]; + + if (info->cap_mode) { + EV_SET(&ev[0], p->pfd, EVFILT_PROCDESC, EV_DELETE, 0, 0, 0); + (void)kevent(info->pdkq, ev, nitems(ev), NULL, 0, 0); + close(p->pfd); + } LIST_FOREACH_SAFE(t, &p->threadlist, entries, t2) { free(t); @@ -373,8 +503,8 @@ struct procinfo *p, *p2; LIST_FOREACH_SAFE(p, &info->proclist, entries, p2) { - detach_proc(p->pid); - free_proc(p); + detach_proc(info, p); + free_proc(info, p); } } @@ -616,9 +746,9 @@ assert(LIST_NEXT(LIST_FIRST(&p->threadlist), entries) == NULL); p->abi = find_abi(p->pid); if (p->abi == NULL) { - if (ptrace(PT_DETACH, p->pid, (caddr_t)1, 0) < 0) + if (truss_ptrace(info, PT_DETACH, p, (caddr_t)1, 0) < 0) err(1, "Can not detach the process"); - free_proc(p); + free_proc(info, p); } } } @@ -701,6 +831,9 @@ struct threadinfo *t; t = info->curthread; + if (t->proc->herald_printed) + return; + t->proc->herald_printed = true; clock_gettime(CLOCK_REALTIME, &t->after); t->before = t->after; print_line_prefix(info); @@ -780,6 +913,85 @@ } +static void +eventloop_handle_trapped(struct trussinfo *info, pid_t si_pid, int si_status, + siginfo_t *si) +{ + struct ptrace_lwpinfo pl; + int pending_signal; + + if (ptrace(PT_LWPINFO, si_pid, (caddr_t)&pl, sizeof(pl)) == -1) + err(1, "ptrace(PT_LWPINFO)"); + + if ((pl.pl_flags & PL_FLAG_CHILD) != 0) { + new_proc(info, si_pid, pl.pl_lwpid, -1, true, false); + assert(LIST_FIRST(&info->proclist)->abi != NULL); + } else if ((pl.pl_flags & PL_FLAG_BORN) != 0) { + new_thread(find_proc(info, si_pid), + pl.pl_lwpid); + } + find_thread(info, si_pid, pl.pl_lwpid); + + pending_signal = 0; + if (si_status == SIGTRAP && (pl.pl_flags & (PL_FLAG_BORN | + PL_FLAG_EXITED | PL_FLAG_SCE | PL_FLAG_SCX)) != 0) { + if ((pl.pl_flags & PL_FLAG_BORN) != 0) { + if ((info->flags & COUNTONLY) == 0) + report_thread_birth(info); + } else if ((pl.pl_flags & PL_FLAG_EXITED) != 0) { + if ((info->flags & COUNTONLY) == 0) + report_thread_death(info); + free_thread(info->curthread); + info->curthread = NULL; + } else if ((pl.pl_flags & PL_FLAG_SCE) != 0) { + enter_syscall(info, info->curthread, &pl); + } else if ((pl.pl_flags & PL_FLAG_SCX) != 0) { + exit_syscall(info, &pl); + } + } else if ((pl.pl_flags & PL_FLAG_CHILD) != 0) { + if ((info->flags & COUNTONLY) == 0) + report_new_child(info); + } else if (si != NULL) { + if ((info->flags & NOSIGS) == 0) + report_signal(info, si, &pl); + pending_signal = si->si_status; + } + if (ptrace(PT_SYSCALL, si_pid, (caddr_t)1, pending_signal) == -1) + err(1, "ptrace(PT_SYSCALL)"); +} + +static void +eventloop_handle_note_fork(struct trussinfo *info) +{ + struct ptrace_child *ptcs; + int cnt, i; + + cnt = ptrace(PT_GET_CHILDREN, getpid(), NULL, 0); + if (cnt == -1) + err(1, "Unexpected error from ptrace(PT_GET_CHILDREN) size"); + if (cnt == 0) + return; + ptcs = calloc(cnt, sizeof(*ptcs)); + if (ptcs == NULL) + err(1, "No memory"); + cnt = ptrace(PT_GET_CHILDREN, getpid(), (caddr_t)ptcs, + cnt * sizeof(*ptcs)); + if (cnt == -1) + err(1, "Unexpected error from ptrace(PT_GET_CHILDREN) data"); + for (i = 0; i < cnt; i++) { + if ((ptcs[i].flags & (PTCHLD_TRACED | PTCHLD_TRACED_BY_ME | + PTCHLD_EXITED)) != (PTCHLD_TRACED | PTCHLD_TRACED_BY_ME)) + continue; + if (new_proc(info, ptcs[i].pid, 0, -1, true, true)) { + if ((info->flags & COUNTONLY) == 0) + report_new_child(info); + eventloop_handle_trapped(info, ptcs[i].pid, SIGTRAP, + NULL); + } + } + free(ptcs); +} + /* * Wait for events until all the processes have exited or truss has been * asked to stop. @@ -787,9 +999,10 @@ void eventloop(struct trussinfo *info) { - struct ptrace_lwpinfo pl; siginfo_t si; - int pending_signal; + struct kevent ev[1]; + int cnt, error; + bool has_si; while (!LIST_EMPTY(&info->proclist)) { if (detaching) { @@ -797,11 +1010,43 @@ return; } - if (waitid(P_ALL, 0, &si, WTRAPPED | WEXITED) == -1) { - if (errno == EINTR) + has_si = false; + if (info->cap_mode) { + cnt = kevent(info->pdkq, NULL, 0, ev, nitems(ev), + NULL); + if (cnt == -1) { + if (errno == EINTR) + continue; + err(1, "Unexpected error from kevent"); + } + if (cnt == 0) { + /* XXXKIB ? */ continue; - err(1, "Unexpected error from waitid"); + } + if ((ev[0].fflags & (NOTE_EXIT | NOTE_PDSIGCHLD)) != + 0) { + error = pdwait(ev[0].ident, NULL, + WFLAGS | WNOHANG, NULL, &si); + if (error == -1) { + if (errno == EINTR || + errno == EWOULDBLOCK) + continue; + err(1, "Unexpected error from pdwait"); + } + has_si = true; + } + if ((ev[0].fflags & NOTE_FORK) != 0) + eventloop_handle_note_fork(info); + } else { + if (waitid(P_ALL, 0, &si, WTRAPPED | WEXITED) == -1) { + if (errno == EINTR) + continue; + err(1, "Unexpected error from waitid"); + } + has_si = true; } + if (!has_si) + continue; assert(si.si_signo == SIGCHLD); @@ -815,50 +1060,12 @@ thread_exit_syscall(info); report_exit(info, &si); } - free_proc(info->curthread->proc); + free_proc(info, info->curthread->proc); info->curthread = NULL; break; case CLD_TRAPPED: - if (ptrace(PT_LWPINFO, si.si_pid, (caddr_t)&pl, - sizeof(pl)) == -1) - err(1, "ptrace(PT_LWPINFO)"); - - if (pl.pl_flags & PL_FLAG_CHILD) { - new_proc(info, si.si_pid, pl.pl_lwpid); - assert(LIST_FIRST(&info->proclist)->abi != - NULL); - } else if (pl.pl_flags & PL_FLAG_BORN) - new_thread(find_proc(info, si.si_pid), - pl.pl_lwpid); - find_thread(info, si.si_pid, pl.pl_lwpid); - - if (si.si_status == SIGTRAP && - (pl.pl_flags & (PL_FLAG_BORN|PL_FLAG_EXITED| - PL_FLAG_SCE|PL_FLAG_SCX)) != 0) { - if (pl.pl_flags & PL_FLAG_BORN) { - if ((info->flags & COUNTONLY) == 0) - report_thread_birth(info); - } else if (pl.pl_flags & PL_FLAG_EXITED) { - if ((info->flags & COUNTONLY) == 0) - report_thread_death(info); - free_thread(info->curthread); - info->curthread = NULL; - } else if (pl.pl_flags & PL_FLAG_SCE) - enter_syscall(info, info->curthread, &pl); - else if (pl.pl_flags & PL_FLAG_SCX) - exit_syscall(info, &pl); - pending_signal = 0; - } else if (pl.pl_flags & PL_FLAG_CHILD) { - if ((info->flags & COUNTONLY) == 0) - report_new_child(info); - pending_signal = 0; - } else { - if ((info->flags & NOSIGS) == 0) - report_signal(info, &si, &pl); - pending_signal = si.si_status; - } - ptrace(PT_SYSCALL, si.si_pid, (caddr_t)1, - pending_signal); + eventloop_handle_trapped(info, si.si_pid, + si.si_status, &si); break; case CLD_STOPPED: errx(1, "waitid reported CLD_STOPPED"); diff --git a/usr.bin/truss/truss.h b/usr.bin/truss/truss.h --- a/usr.bin/truss/truss.h +++ b/usr.bin/truss/truss.h @@ -106,7 +106,9 @@ struct procinfo { LIST_ENTRY(procinfo) entries; pid_t pid; + int pfd; struct procabi *abi; + bool herald_printed; LIST_HEAD(, threadinfo) threadlist; LIST_HEAD(, fd_domain) fdlist; @@ -117,6 +119,9 @@ int flags; int strsize; FILE *outfile; + int pdkq; + bool cap_mode; + bool force_cap_mode; struct timespec start_time; diff --git a/usr.bin/truss/truss.1 b/usr.bin/truss/truss.1 --- a/usr.bin/truss/truss.1 +++ b/usr.bin/truss/truss.1 @@ -7,7 +7,7 @@ .Nd trace system calls .Sh SYNOPSIS .Nm -.Op Fl facedDHS +.Op Fl facedyDHSY .Op Fl o Ar file .Op Fl s Ar strsize .Fl p Ar pid @@ -62,6 +62,22 @@ (Normally, .Nm displays signal as well as system call events.) +.It Fl y +Enter the capability mode +.Pq see Xr capsicum 4 +after opening the output file, and creating and +attaching to the target process. +The error to enter the mode is not fatal, but a warning is +issued to the standard error. +.It Fl Y +Same as +.Fl y , +but make the error to enter the capability mode fatal. +Since the traced process is spawn and attached to +before the entry into the mode, it is killed by +the termination of the +.Nm +itself. .It Fl o Ar file Print the output to the specified .Ar file