Hello once again, I have CC'd the original developers of tame/pledge; hope that is not in violation of etiquette? This is my last post on this subject (or to any devs emails) if interest remains cool, but it deserves a fair shot, and after all, 500 hours is nothing to sneeze at!
These non-invasive additions apply cleanly to the latest code of the three affected source files. I admit I am still running -current from mid-April, so I can't test it with these latest files, but the only change since my mid-April versions was to syscalls.master, and merged easily. https://fremissant.net/pledge This doesn't include patches to insert into <unistd.h>, but it's sufficient for the present, so that pledge(1) can run at full capabilities for anyone who has the patched kernel. Also, the pledge.2 mdoc patch is not included; but is available at that URL. I don't think the patch is too long to include on tech@? Copied below. The future of pledge(1) depends on your decision. Thanks for your consideration, Andy. Index: sys/kern/kern_pledge.c =================================================================== RCS file: /cvs/src/sys/kern/kern_pledge.c,v retrieving revision 1.251 diff -u -p -r1.251 kern_pledge.c --- sys/kern/kern_pledge.c 14 Feb 2019 15:41:47 -0000 1.251 +++ sys/kern/kern_pledge.c 11 May 2019 23:50:24 -0000 @@ -233,6 +233,11 @@ const uint64_t pledge_syscalls[SYS_MAXSY [SYS_wait4] = PLEDGE_STDIO, + [SYS_pledgepid] = PLEDGE_ALWAYS, +#if 0 + [SYS_pledgepid] = PLEDGE_PROC, /* if choose this, move it down */ +#endif + /* * Can kill self with "stdio". Killing another pid * requires "proc" @@ -500,6 +512,219 @@ sys_pledge(struct proc *p, void *v, regi pr->ps_execpledge = execpromises; pr->ps_flags |= PS_EXECPLEDGE; } + return (0); +} + +/* + * To query, PLEDGEPID_QUERY opts bit must be set, and results are + * returned through copyout() to the pbits/epbits pointers. + * To set/drop [exec]promises, set opts PLEDGEPID_SET/DROP_[E]PROMS; + * the promise data are read from [e]pbits. + * It is permissible to combine query and set/drop. + * Query only affects the components (pbits/epbits) which were + * passed non-NULL. Passing NULL with set/drop causes current + * pledge state in that component to remain unchanged. + * Caveat: query destructively overwrites userland data pointed to + * by pbits/epbits (when not NULL). + * ----- + * [ based on sys_pledge(), sys/kern/kern_sig.c:cansignal(),sys_kill() ] + */ +int +sys_pledgepid(struct proc *caller_p, void *v, register_t *retval) +{ + struct sys_pledgepid_args /* { + syscallarg(pid_t)pid; + syscallarg(uint32_t)opts; + syscallarg(uint64_t *)pbits; + syscallarg(uint64_t *)epbits; + syscallarg(const void *)aux; + } */ *uap = v; + uint32_t opts = SCARG(uap, opts); + uint64_t u64tmp, *pbits = NULL, *epbits = NULL; + struct process *caller_pr = caller_p->p_p; + struct process *target_pr; /* will be proc. corresp. to pid arg. */ + pid_t pid_caller = caller_pr->ps_pid; + pid_t pid_target = SCARG(uap, pid); + /* see kern_sig.c:cansignal() */ + struct ucred *caller_p_uc = caller_p->p_ucred; + /* ...and not caller_pr->ps_ucred... */ + struct ucred *target_pr_uc; /* will use (*process)->ps_ucred */ + int empowered = 0; + int error; + + /* + * If caller is under pledge, and pid argument is not that of + * the caller (or 0), fail immediately unless hold "proc" promise. + */ + if ((error = pledge_kill(caller_p, pid_target)) != 0) + return (error); + + if (pid_target < 0) + return (EINVAL); + + if (pid_target == 0) { + pid_target = pid_caller; + target_pr = caller_pr; + } else if((target_pr = prfind(pid_target)) == NULL) { + if ((target_pr = zombiefind(pid_target)) == NULL) + return (ESRCH); + else { + /* + * Let it go ahead and try to act on the zombie; if + * it's possible to pledge it down as requested, so + * much the better, zombie or not. :) + */ +#if 0 +/* (This happens quite often when pledge(1) tries to set post-init promises + * on a command which finishes sooner than the default 400 ms delay.) + */ + zombie = 1; + uprintf("pledgepid: warn: [%d] zombie\n", pid_target); +#endif + } + } + + target_pr_uc = target_pr->ps_ucred; + + if (opts & (PLEDGEPID_SET_PROMS | PLEDGEPID_SET_EPROMS | + PLEDGEPID_DROP_PROMS | PLEDGEPID_DROP_EPROMS)) { + /* Copy them now, or query will stomp. */ + if (SCARG(uap, pbits) != NULL) { + pbits = malloc(sizeof(*pbits), M_TEMP, M_WAITOK); + if (copyin(SCARG(uap, pbits), pbits, sizeof(*pbits))) + return (EFAULT); + *pbits &= PLEDGE_USERSET; + } + if (SCARG(uap, epbits) != NULL) { + epbits = malloc(sizeof(*epbits), M_TEMP, M_WAITOK); + if (copyin(SCARG(uap, epbits), epbits, sizeof(*epbits))) + return (EFAULT); + *epbits &= PLEDGE_USERSET; + } + } + + /* + * At present, any process can query any other process that it + * can signal. Another possibility would be to only allow querying + * when the caller has uid 0. The highest nibble is reserved for + * kernel use, so it is safe to repurpose a high bit to additionally + * convey the pr->flags PS_PLEDGE/PS_EXECPLEDGE status to userland. + */ + if (opts & PLEDGEPID_QUERY) { + if (SCARG(uap, pbits) != NULL) { + u64tmp = target_pr->ps_pledge & PLEDGE_USERSET; + if (!ISSET(target_pr->ps_flags, PS_PLEDGE)) + u64tmp |= PLEDGEPID_UNPLEDGED; + if (copyout(&u64tmp, SCARG(uap, pbits), 8)) + return (EFAULT); + } + if (SCARG(uap, epbits) != NULL) { + u64tmp = target_pr->ps_execpledge & PLEDGE_USERSET; + if (!ISSET(target_pr->ps_flags, PS_EXECPLEDGE)) + u64tmp |= PLEDGEPID_UNPLEDGED; + if (copyout(&u64tmp, SCARG(uap, epbits), 8)) + return (EFAULT); + } + } + + if (!(opts & PLEDGEPID_MODSET)) + return (0); + + /* + * Note that the additional checks that promises are not augmented + * are performed below; not even root is allowed to add promises! + */ + + if (caller_p_uc->cr_uid == 0) + empowered = 1; /* root can always pledge any pid */ + + if (caller_pr == target_pr) + empowered = 1; /* process can always pledge itself */ + + /* see sys/kern/kern_sig.c:cansignal() */ + if (!empowered && + !(caller_p_uc->cr_ruid == target_pr_uc->cr_ruid || + caller_p_uc->cr_ruid == target_pr_uc->cr_svuid || + caller_p_uc->cr_uid == target_pr_uc->cr_ruid || + caller_p_uc->cr_uid == target_pr_uc->cr_svuid)) + return (EPERM); + + if (pbits == NULL) + goto pledgepid_skip_1; + + if (opts & PLEDGEPID_DROP_PROMS) { + /* Never augments promises */ + u64tmp = target_pr->ps_pledge; + u64tmp &= ~*pbits; /* already masked by PLEDGE_USERSET */ + target_pr->ps_pledge = u64tmp; +#if 1 + target_pr->ps_flags |= PS_PLEDGE; +#else + atomic_setbits_int(&target_pr->ps_flags, PS_PLEDGE); +#endif + } else if (opts & PLEDGEPID_SET_PROMS) { + /* In "error" mode, ignore promise increase requests, + * but accept promise decrease requests */ + if (ISSET(target_pr->ps_flags, PS_PLEDGE) && + (target_pr->ps_pledge & PLEDGE_ERROR)) + *pbits &= target_pr->ps_pledge; + + /* Only permit reductions */ + if (ISSET(target_pr->ps_flags, PS_PLEDGE) && + (((*pbits | target_pr->ps_pledge) != + target_pr->ps_pledge))) + return (EPERM); + + target_pr->ps_pledge = *pbits; + target_pr->ps_flags |= PS_PLEDGE; + } + +pledgepid_skip_1: + + if (epbits == NULL) + goto pledgepid_skip_2; + + if (opts & PLEDGEPID_DROP_EPROMS) { + /* Never augments execpromises */ + u64tmp = target_pr->ps_execpledge; + u64tmp &= ~*epbits; /* already masked by PLEDGE_USERSET */ + target_pr->ps_execpledge = u64tmp; +#if 1 + target_pr->ps_flags |= PS_EXECPLEDGE; +#else + atomic_setbits_int(&target_pr->ps_flags, PS_EXECPLEDGE); +#endif + } else if (opts & PLEDGEPID_SET_EPROMS) { + /* XXX should not such a case also be in pledge(2)? */ + if (ISSET(target_pr->ps_flags, PS_PLEDGE) && + (target_pr->ps_pledge & PLEDGE_ERROR)) + *epbits &= target_pr->ps_execpledge; + + /* Only permit reductions */ + if (ISSET(target_pr->ps_flags, PS_EXECPLEDGE) && + (((*epbits | target_pr->ps_execpledge) != + target_pr->ps_execpledge))) + return (EPERM); + + target_pr->ps_execpledge = *epbits; + target_pr->ps_flags |= PS_EXECPLEDGE; + } + +pledgepid_skip_2: + if (pbits != NULL) + free(pbits, M_TEMP, sizeof(*pbits)); + if (epbits != NULL) + free(epbits, M_TEMP, sizeof(*epbits)); + + /* + * Kill off unveil and drop unveil vnode refs if target process + * no longer holds any path-accessing pledge. + */ + if ((target_pr->ps_pledge & (PLEDGE_RPATH | PLEDGE_WPATH | + PLEDGE_CPATH | PLEDGE_DPATH | PLEDGE_TMPPATH | PLEDGE_EXEC | + PLEDGE_UNIX | PLEDGE_UNVEIL)) == 0) + unveil_destroy(target_pr); + return (0); } Index: sys/kern/syscalls.master =================================================================== RCS file: /cvs/src/sys/kern/syscalls.master,v retrieving revision 1.190 diff -u -p -r1.190 syscalls.master --- sys/kern/syscalls.master 9 May 2019 20:30:22 -0000 1.190 +++ sys/kern/syscalls.master 11 May 2019 23:50:25 -0000 @@ -226,7 +226,7 @@ 106 STD { int sys_listen(int s, int backlog); } 107 STD { int sys_chflagsat(int fd, const char *path, \ u_int flags, int atflags); } -108 STD { int sys_pledge(const char *promises, \ +108 STD { int sys_pledge(const char *promises, \ const char *execpromises); } 109 STD { int sys_ppoll(struct pollfd *fds, \ u_int nfds, const struct timespec *ts, \ @@ -238,7 +238,7 @@ 112 STD { int sys_sendsyslog(const char *buf, size_t nbyte, \ int flags); } 113 UNIMPL fktrace -114 STD { int sys_unveil(const char *path, \ +114 STD { int sys_unveil(const char *path, \ const char *permissions); } 115 OBSOL vtrace 116 OBSOL t32_gettimeofday @@ -565,3 +565,6 @@ 328 OBSOL __tfork51 329 STD NOLOCK { void sys___set_tcb(void *tcb); } 330 STD NOLOCK { void *sys___get_tcb(void); } +331 STD { int sys_pledgepid(pid_t pid, uint32_t opts, \ + uint64_t *pbits, uint64_t *epbits, \ + const void *aux); } Index: sys/sys/pledge.h =================================================================== RCS file: /cvs/src/sys/sys/pledge.h,v retrieving revision 1.39 diff -u -p -r1.39 pledge.h --- sys/sys/pledge.h 21 Jan 2019 20:09:37 -0000 1.39 +++ sys/sys/pledge.h 11 May 2019 23:50:25 -0000 @@ -23,6 +23,35 @@ #include <sys/cdefs.h> /* + * pledgepid(2) opts flags + */ +/* Permissions allowing, the promises data is returned through pbits/epbits: */ +#define PLEDGEPID_QUERY 0x00000001U +#if 0 +#define PLEDGEPID__unused_1 0x00000002U +#endif +/* + * pledge(2)-style operation: sets initial promises if unpledged in that + * component; otherwise, sets the given promises if they are a subset + * (inclusive) of the target's currently held set; otherwise returns EPERM. + */ +#define PLEDGEPID_SET_PROMS 0x00000004U +#define PLEDGEPID_SET_EPROMS 0x00000008U +/* Subtractive operation: remove specified promises from an existing pledge: */ +#define PLEDGEPID_DROP_PROMS 0x00000010U +#define PLEDGEPID_DROP_EPROMS 0x00000020U +/* + * The 4 high bits of uint64_t promises/execpromises are available for + * dual (independent) use kernel/userland; these are for userland. + */ +#if 0 +#define PLEDGEPID__unused_2 0x1000000000000000ULL +#define PLEDGEPID__unused_3 0x2000000000000000ULL +#endif +#define PLEDGEPID_SUBTRACTIVE 0x4000000000000000ULL +#define PLEDGEPID_UNPLEDGED 0x8000000000000000ULL + +/* * pledge(2) requests */ #define PLEDGE_ALWAYS 0xffffffffffffffffULL @@ -50,6 +79,7 @@ #define PLEDGE_MCAST 0x0000000000200000ULL /* multicast joins */ #define PLEDGE_VMINFO 0x0000000000400000ULL /* vminfo listings */ #define PLEDGE_PS 0x0000000000800000ULL /* ps listings */ +#define PLEDGE_seniuk 0x0000000001000000ULL /* XXX reserved ;) */ #define PLEDGE_DISKLABEL 0x0000000002000000ULL /* disklabels */ #define PLEDGE_PF 0x0000000004000000ULL /* pf ioctls */ #define PLEDGE_AUDIO 0x0000000008000000ULL /* audio ioctls */
