diff --git a/include/unistd.h b/include/unistd.h --- a/include/unistd.h +++ b/include/unistd.h @@ -465,6 +465,7 @@ #if (__XSI_VISIBLE && __XSI_VISIBLE <= 500) || __BSD_VISIBLE int brk(const void *); int chroot(const char *); +int fchroot(int); int getdtablesize(void); int getpagesize(void) __pure2; char *getpass(const char *); diff --git a/lib/libsys/Makefile.sys b/lib/libsys/Makefile.sys --- a/lib/libsys/Makefile.sys +++ b/lib/libsys/Makefile.sys @@ -417,6 +417,7 @@ MLINKS+=chown.2 fchown.2 \ chown.2 fchownat.2 \ chown.2 lchown.2 +MLINKS+=chroot.2 fchroot.2 MLINKS+=clock_gettime.2 clock_getres.2 \ clock_gettime.2 clock_settime.2 MLINKS+=closefrom.2 close_range.2 diff --git a/lib/libsys/Symbol.sys.map b/lib/libsys/Symbol.sys.map --- a/lib/libsys/Symbol.sys.map +++ b/lib/libsys/Symbol.sys.map @@ -429,6 +429,7 @@ FBSD_1.8 { kcmp; + fchroot; }; FBSDprivate_1.0 { diff --git a/lib/libsys/chroot.2 b/lib/libsys/chroot.2 --- a/lib/libsys/chroot.2 +++ b/lib/libsys/chroot.2 @@ -29,7 +29,8 @@ .Dt CHROOT 2 .Os .Sh NAME -.Nm chroot +.Nm chroot , +.Nm fchroot .Nd change root directory .Sh LIBRARY .Lb libc @@ -37,6 +38,8 @@ .In unistd.h .Ft int .Fn chroot "const char *dirname" +.Ft int +.Fn fchroot "int fd" .Sh DESCRIPTION The .Fa dirname @@ -92,6 +95,12 @@ mimicking the historic insecure behavior of .Fn chroot still present on other systems. +.Pp +The +.Fn fchroot +is identical to +.Fn chroot +except it takes a file descriptor instead of path. .Sh RETURN VALUES .Rv -std .Sh ERRORS diff --git a/lib/libsys/syscalls.map b/lib/libsys/syscalls.map --- a/lib/libsys/syscalls.map +++ b/lib/libsys/syscalls.map @@ -801,4 +801,6 @@ __sys_timerfd_settime; _kcmp; __sys_kcmp; + _fchroot; + __sys_fchroot; }; 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 @@ -164,9 +164,6 @@ .It Dv CAP_EXTATTR_SET Permit .Xr extattr_set_fd 2 . -.It Dv CAP_FCHDIR -Permit -.Xr fchdir 2 . .It Dv CAP_FCHFLAGS Permit .Xr fchflags 2 diff --git a/sys/kern/imgact_elf.c b/sys/kern/imgact_elf.c --- a/sys/kern/imgact_elf.c +++ b/sys/kern/imgact_elf.c @@ -792,15 +792,6 @@ u_long base_addr = 0; int error; -#ifdef CAPABILITY_MODE - /* - * XXXJA: This check can go away once we are sufficiently confident - * that the checks in namei() are correct. - */ - if (IN_CAPABILITY_MODE(curthread)) - return (ECAPMODE); -#endif - tempdata = malloc(sizeof(*tempdata), M_TEMP, M_WAITOK | M_ZERO); nd = &tempdata->nd; attr = &tempdata->attr; diff --git a/sys/kern/kern_exec.c b/sys/kern/kern_exec.c --- a/sys/kern/kern_exec.c +++ b/sys/kern/kern_exec.c @@ -453,18 +453,6 @@ interpret: if (args->fname != NULL) { -#ifdef CAPABILITY_MODE - /* - * While capability mode can't reach this point via direct - * path arguments to execve(), we also don't allow - * interpreters to be used in capability mode (for now). - * Catch indirect lookups and return a permissions error. - */ - if (IN_CAPABILITY_MODE(td)) { - error = ECAPMODE; - goto exec_fail; - } -#endif /* * Translate the file name. namei() returns a vnode diff --git a/sys/kern/kern_mib.c b/sys/kern/kern_mib.c --- a/sys/kern/kern_mib.c +++ b/sys/kern/kern_mib.c @@ -98,7 +98,7 @@ SYSCTL_INT(_kern, KERN_OSREV, osrevision, CTLFLAG_RD | CTLFLAG_CAPRD, SYSCTL_NULL_INT_PTR, BSD, "Operating system revision"); -SYSCTL_STRING(_kern, KERN_VERSION, version, CTLFLAG_RD, +SYSCTL_STRING(_kern, KERN_VERSION, version, CTLFLAG_RD | CTLFLAG_CAPRD, version, 0, "Kernel version"); SYSCTL_STRING(_kern, OID_AUTO, compiler_version, CTLFLAG_RD, 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 @@ -55,7 +55,6 @@ __read_mostly cap_rights_t cap_chflags_rights; __read_mostly cap_rights_t cap_connect_rights; __read_mostly cap_rights_t cap_event_rights; -__read_mostly cap_rights_t cap_fchdir_rights; __read_mostly cap_rights_t cap_fchflags_rights; __read_mostly cap_rights_t cap_fchmod_rights; __read_mostly cap_rights_t cap_fchown_rights; @@ -96,6 +95,7 @@ __read_mostly cap_rights_t cap_unlinkat_rights; __read_mostly cap_rights_t cap_write_rights; __read_mostly cap_rights_t cap_no_rights; +__read_mostly cap_rights_t cap_all_rights; static void cap_rights_sysinit(void *arg) @@ -104,7 +104,6 @@ cap_rights_init_one(&cap_bind_rights, CAP_BIND); cap_rights_init_one(&cap_connect_rights, CAP_CONNECT); cap_rights_init_one(&cap_event_rights, CAP_EVENT); - cap_rights_init_one(&cap_fchdir_rights, CAP_FCHDIR); cap_rights_init_one(&cap_fchflags_rights, CAP_FCHFLAGS); cap_rights_init_one(&cap_fchmod_rights, CAP_FCHMOD); cap_rights_init_one(&cap_fchown_rights, CAP_FCHOWN); @@ -145,6 +144,8 @@ cap_rights_init_one(&cap_unlinkat_rights, CAP_UNLINKAT); cap_rights_init_one(&cap_write_rights, CAP_WRITE); cap_rights_init(&cap_no_rights); + cap_rights_init(&cap_all_rights); + CAP_ALL(&cap_all_rights); } SYSINIT(cap_rights_sysinit, SI_SUB_COPYRIGHT, SI_ORDER_ANY, cap_rights_sysinit, NULL); diff --git a/sys/kern/sys_capability.c b/sys/kern/sys_capability.c --- a/sys/kern/sys_capability.c +++ b/sys/kern/sys_capability.c @@ -76,6 +76,7 @@ #include #include #include +#include #include #include @@ -101,6 +102,15 @@ { struct ucred *newcred, *oldcred; struct proc *p; + struct vnode *ecapmodevp; + + // XXX: ecapmodevp should be global + // XXX: ecapmode_vnodeops, to return the right errno? make sure we're returning the wrong one now. + getnewvnode("ecapmode", NULL, &dead_vnodeops, &ecapmodevp); + ecapmodevp->v_state = VSTATE_CONSTRUCTED; // XXX: needed? + + pwd_chdir(td, ecapmodevp); + pwd_chroot(td, ecapmodevp); if (IN_CAPABILITY_MODE(td)) return (0); diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master --- a/sys/kern/syscalls.master +++ b/sys/kern/syscalls.master @@ -143,7 +143,7 @@ size_t nbyte ); } -5 AUE_OPEN_RWTC STD { +5 AUE_OPEN_RWTC STD|CAPENABLED { int open( _In_z_ const char *path, int flags, @@ -157,7 +157,7 @@ int fd ); } -7 AUE_WAIT4 STD { +7 AUE_WAIT4 STD|CAPENABLED { int wait4( int pid, _Out_opt_ int *status, @@ -177,18 +177,18 @@ _In_z_ const char *link ); } -10 AUE_UNLINK STD { +10 AUE_UNLINK STD|CAPENABLED { int unlink( _In_z_ const char *path ); } 11 AUE_NULL OBSOL execv -12 AUE_CHDIR STD { +12 AUE_CHDIR STD|CAPENABLED { int chdir( _In_z_ const char *path ); } -13 AUE_FCHDIR STD { +13 AUE_FCHDIR STD|CAPENABLED { int fchdir( int fd ); @@ -200,7 +200,7 @@ uint32_t dev ); } -15 AUE_CHMOD STD { +15 AUE_CHMOD STD|CAPENABLED { int chmod( _In_z_ const char *path, mode_t mode @@ -313,7 +313,7 @@ _Inout_ __socklen_t *alen ); } -33 AUE_ACCESS STD { +33 AUE_ACCESS STD|CAPENABLED { int access( _In_z_ const char *path, int amode @@ -443,20 +443,20 @@ _In_z_ const char *path ); } -57 AUE_SYMLINK STD { +57 AUE_SYMLINK STD|CAPENABLED { int symlink( _In_z_ const char *path, _In_z_ const char *link ); } -58 AUE_READLINK STD { +58 AUE_READLINK STD|CAPENABLED { ssize_t readlink( _In_z_ const char *path, _Out_writes_z_(count) char *buf, size_t count ); } -59 AUE_EXECVE STD { +59 AUE_EXECVE STD|CAPENABLED { int execve( _In_z_ const char *fname, _In_z_ char **argv, @@ -497,7 +497,7 @@ int flags ); } -66 AUE_VFORK STD { +66 AUE_VFORK STD|CAPENABLED { int vfork(void); } 67 AUE_NULL OBSOL vread @@ -650,7 +650,7 @@ int protocol ); } -98 AUE_CONNECT STD { +98 AUE_CONNECT STD|CAPENABLED { int connect( int s, _In_reads_bytes_(namelen) const struct sockaddr *name, @@ -836,7 +836,7 @@ int egid ); } -128 AUE_RENAME STD { +128 AUE_RENAME STD|CAPENABLED { int rename( _In_z_ const char *from, _In_z_ const char *to @@ -890,7 +890,7 @@ _Out_writes_(2) int *rsv ); } -136 AUE_MKDIR STD { +136 AUE_MKDIR STD|CAPENABLED { int mkdir( _In_z_ const char *path, mode_t mode @@ -1146,7 +1146,7 @@ _Out_ _Contains_timet_ struct freebsd11_stat *ub ); } -191 AUE_PATHCONF STD { +191 AUE_PATHCONF STD|CAPENABLED { int pathconf( _In_z_ const char *path, int name @@ -1455,7 +1455,7 @@ int inherit ); } -251 AUE_RFORK STD { +251 AUE_RFORK STD|CAPENABLED { int rfork( int flags ); @@ -1692,7 +1692,7 @@ 325 AUE_MUNLOCKALL STD|CAPENABLED { int munlockall(void); } -326 AUE_GETCWD STD { +326 AUE_GETCWD STD|CAPENABLED { int __getcwd( _Out_writes_z_(buflen) char *buf, size_t buflen @@ -1980,7 +1980,7 @@ ); } 375 AUE_NULL OBSOL nfsclnt -376 AUE_EACCESS STD { +376 AUE_EACCESS STD|CAPENABLED { int eaccess( _In_z_ const char *path, int amode @@ -2243,7 +2243,7 @@ _In_z_ const char *name ); } -425 AUE_ACL_GET_LINK STD { +425 AUE_ACL_GET_LINK STD|CAPENABLED { int __acl_get_link( _In_z_ const char *path, acl_type_t type, @@ -2819,7 +2819,7 @@ _Inout_opt_ _Contains_long_ struct shmid_ds *buf ); } -513 AUE_LPATHCONF STD { +513 AUE_LPATHCONF STD|CAPENABLED { int lpathconf( _In_z_ const char *path, int name @@ -3234,7 +3234,7 @@ _Inout_opt_ uint32_t *ptr ); } -574 AUE_REALPATHAT STD { +574 AUE_REALPATHAT STD|CAPENABLED { int __realpathat( int fd, _In_z_ const char *path, @@ -3333,5 +3333,11 @@ uintptr_t idx2 ); } +589 AUE_NULL STD|CAPENABLED { + int fchroot( + int fd + ); + } + ; vim: syntax=off diff --git a/sys/kern/uipc_syscalls.c b/sys/kern/uipc_syscalls.c --- a/sys/kern/uipc_syscalls.c +++ b/sys/kern/uipc_syscalls.c @@ -467,11 +467,6 @@ struct file *fp; int error; -#ifdef CAPABILITY_MODE - if (IN_CAPABILITY_MODE(td) && (dirfd == AT_FDCWD)) - return (ECAPMODE); -#endif - AUDIT_ARG_FD(fd); AUDIT_ARG_SOCKADDR(td, dirfd, sa); error = getsock(td, fd, &cap_connect_rights, &fp); diff --git a/sys/kern/vfs_lookup.c b/sys/kern/vfs_lookup.c --- a/sys/kern/vfs_lookup.c +++ b/sys/kern/vfs_lookup.c @@ -271,13 +271,6 @@ struct componentname *cnp; cnp = &ndp->ni_cnd; - if ((ndp->ni_lcf & NI_LCF_STRICTRELATIVE) != 0) { -#ifdef KTRACE - if (KTRPOINT(curthread, KTR_CAPFAIL)) - ktrcapfail(CAPFAIL_LOOKUP, NULL, NULL); -#endif - return (ENOTCAPABLE); - } while (*(cnp->cn_nameptr) == '/') { cnp->cn_nameptr++; ndp->ni_pathlen--; @@ -318,15 +311,9 @@ * the relative root. */ if (IN_CAPABILITY_MODE(td) && (cnp->cn_flags & NOCAPCHECK) == 0) { + // XXX: also set NI_LCF_CAP_DOTDOT? ndp->ni_lcf |= NI_LCF_STRICTRELATIVE; ndp->ni_resflags |= NIRES_STRICTREL; - if (ndp->ni_dirfd == AT_FDCWD) { -#ifdef KTRACE - if (KTRPOINT(td, KTR_CAPFAIL)) - ktrcapfail(CAPFAIL_LOOKUP, NULL, NULL); -#endif - return (ECAPMODE); - } } #endif error = 0; @@ -344,6 +331,7 @@ if (cnp->cn_pnbuf[0] == '/') { ndp->ni_resflags |= NIRES_ABS; + CAP_ALL(&ndp->ni_filecaps.fc_rights); error = namei_handle_root(ndp, dpp); } else { if (ndp->ni_startdir != NULL) { @@ -351,6 +339,7 @@ startdir_used = true; } else if (ndp->ni_dirfd == AT_FDCWD) { *dpp = pwd->pwd_cdir; + CAP_ALL(&ndp->ni_filecaps.fc_rights); vrefact(*dpp); } else { if (cnp->cn_flags & AUDITVNODE1) diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c --- a/sys/kern/vfs_syscalls.c +++ b/sys/kern/vfs_syscalls.c @@ -898,7 +898,12 @@ int error; AUDIT_ARG_FD(uap->fd); - error = getvnode_path(td, uap->fd, &cap_fchdir_rights, + /* + * XXX: Instead of cap_fchdir_rights we require file descriptors + * passed to fchdir(2) to have full rights, so then we don't + * need to track those rights for process' cwd and root. + */ + error = getvnode_path(td, uap->fd, &cap_all_rights, &fp); if (error != 0) return (error); @@ -967,18 +972,13 @@ SYSCTL_INT(_security_bsd, OID_AUTO, unprivileged_chroot, CTLFLAG_RW, &unprivileged_chroot, 0, "Unprivileged processes can use chroot(2)"); + /* - * Change notion of root (``/'') directory. + * Takes locked vnode, unlocks it before returning. */ -#ifndef _SYS_SYSPROTO_H_ -struct chroot_args { - char *path; -}; -#endif -int -sys_chroot(struct thread *td, struct chroot_args *uap) +static int +kern_chroot(struct thread *td, struct vnode *vp) { - struct nameidata nd; struct proc *p; int error; @@ -989,30 +989,80 @@ if (unprivileged_chroot == 0 || (p->p_flag2 & P2_NO_NEW_PRIVS) == 0) { PROC_UNLOCK(p); - return (error); + goto e_vunlock; } PROC_UNLOCK(p); } - NDINIT(&nd, LOOKUP, FOLLOW | LOCKSHARED | LOCKLEAF | AUDITVNODE1, - UIO_USERSPACE, uap->path); - error = namei(&nd); - if (error != 0) - return (error); - NDFREE_PNBUF(&nd); - error = change_dir(nd.ni_vp, td); + + error = change_dir(vp, td); if (error != 0) goto e_vunlock; #ifdef MAC - error = mac_vnode_check_chroot(td->td_ucred, nd.ni_vp); + error = mac_vnode_check_chroot(td->td_ucred, vp); if (error != 0) goto e_vunlock; #endif - VOP_UNLOCK(nd.ni_vp); - error = pwd_chroot(td, nd.ni_vp); - vrele(nd.ni_vp); + VOP_UNLOCK(vp); + error = pwd_chroot(td, vp); + vrele(vp); return (error); e_vunlock: - vput(nd.ni_vp); + vput(vp); + return (error); +} + +/* + * Change notion of root (``/'') directory. + */ +#ifndef _SYS_SYSPROTO_H_ +struct chroot_args { + char *path; +}; +#endif +int +sys_chroot(struct thread *td, struct chroot_args *uap) +{ + struct nameidata nd; + int error; + + NDINIT(&nd, LOOKUP, FOLLOW | LOCKSHARED | LOCKLEAF | AUDITVNODE1, + UIO_USERSPACE, uap->path); + error = namei(&nd); + if (error != 0) + return (error); + NDFREE_PNBUF(&nd); + error = kern_chroot(td, nd.ni_vp); + return (error); +} + +#ifndef _SYS_SYSPROTO_H_ +struct fchroot_args { + int fd; +}; +#endif +int +sys_fchroot(struct thread *td, struct fchroot_args *uap) +{ + struct vnode *vp; + struct file *fp; + int error; + + /* + * XXX: I don't understand how it'd interact with pwd_jdir + * in cache_fplookup() and namei_setup(); let's make sure + * it cannot happen for now. + */ + if (jailed(td->td_ucred)) + return (EPERM); + + error = getvnode_path(td, uap->fd, &cap_all_rights, &fp); + if (error != 0) + return (error); + vp = fp->f_vnode; + vrefact(vp); + fdrop(fp, td); + vn_lock(vp, LK_SHARED | LK_RETRY); + error = kern_chroot(td, vp); return (error); } diff --git a/sys/sys/caprights.h b/sys/sys/caprights.h --- a/sys/sys/caprights.h +++ b/sys/sys/caprights.h @@ -59,10 +59,10 @@ #ifdef _KERNEL extern cap_rights_t cap_accept_rights; +extern cap_rights_t cap_all_rights; extern cap_rights_t cap_bind_rights; extern cap_rights_t cap_connect_rights; extern cap_rights_t cap_event_rights; -extern cap_rights_t cap_fchdir_rights; extern cap_rights_t cap_fchflags_rights; extern cap_rights_t cap_fchmod_rights; extern cap_rights_t cap_fchown_rights; diff --git a/sys/sys/capsicum.h b/sys/sys/capsicum.h --- a/sys/sys/capsicum.h +++ b/sys/sys/capsicum.h @@ -114,7 +114,7 @@ #define CAP_LOOKUP CAPRIGHT(0, 0x0000000000000400ULL) /* VFS methods. */ -/* Allows for fchdir(2). */ +/* No longer used. */ #define CAP_FCHDIR CAPRIGHT(0, 0x0000000000000800ULL) /* Allows for fchflags(2). */ #define CAP_FCHFLAGS CAPRIGHT(0, 0x0000000000001000ULL) @@ -451,6 +451,7 @@ int cap_fcntl_check(struct filedesc *fdp, int fd, int cmd); extern bool trap_enotcap; +extern bool old_capsicum_semantics; #else /* !_KERNEL */ diff --git a/usr.bin/procstat/procstat_files.c b/usr.bin/procstat/procstat_files.c --- a/usr.bin/procstat/procstat_files.c +++ b/usr.bin/procstat/procstat_files.c @@ -145,7 +145,6 @@ { CAP_FTRUNCATE, "ft" }, /* VFS methods. */ - { CAP_FCHDIR, "cd" }, { CAP_FCHFLAGS, "cf" }, { CAP_FCHMOD, "cm" }, { CAP_FCHOWN, "cn" }, diff --git a/usr.sbin/chroot/chroot.8 b/usr.sbin/chroot/chroot.8 --- a/usr.sbin/chroot/chroot.8 +++ b/usr.sbin/chroot/chroot.8 @@ -33,6 +33,7 @@ .Nd change root directory .Sh SYNOPSIS .Nm +.Op Fl d .Op Fl G Ar group Ns Op Cm \&, Ns Ar group ... .Op Fl g Ar group .Op Fl u Ar user @@ -51,6 +52,10 @@ .Pp The options are as follows: .Bl -tag -width "-G group[,group ...]" +.It Fl d +Treat +.Ar newroot +as a file descriptor number. .It Fl G Ar group Ns Op Cm \&, Ns Ar group ... Run the command with the permissions of the specified groups. .It Fl g Ar group diff --git a/usr.sbin/chroot/chroot.c b/usr.sbin/chroot/chroot.c --- a/usr.sbin/chroot/chroot.c +++ b/usr.sbin/chroot/chroot.c @@ -30,10 +30,12 @@ */ #include +#include #include #include #include +#include #include #include #include @@ -55,16 +57,22 @@ const char *shell; gid_t gid, *gidlist; uid_t uid; - int arg, ch, error, gids; + int arg, ch, cwd, error, fd, gids; long ngroups_max; - bool nonprivileged; + bool fd_not_path, nonprivileged, capsicate; gid = 0; uid = 0; user = group = grouplist = NULL; - nonprivileged = false; - while ((ch = getopt(argc, argv, "G:g:u:n")) != -1) { + capsicate = fd_not_path = nonprivileged = false; + while ((ch = getopt(argc, argv, "CdG:g:u:n")) != -1) { switch(ch) { + case 'C': + capsicate = true; + break; + case 'd': + fd_not_path = true; + break; case 'u': user = optarg; if (*user == '\0') @@ -153,8 +161,31 @@ err(1, "procctl"); } - if (chdir(argv[0]) == -1 || chroot(".") == -1) - err(1, "%s", argv[0]); + if (fd_not_path) { + if (capsicate) { + cwd = open(".", O_RDONLY | O_DIRECTORY); + if (cwd < 0) + err(1, "."); + error = cap_enter(); + if (error != 0) + err(1, "cap_enter"); + } + + fd = (uid_t)strtoul(argv[0], &endp, 0); + if (*endp != '\0') + errx(1, "%s is not a number", argv[0]); + if (fchdir(cwd) == -1) + err(1, "fchdir"); + if (fchroot(fd) == -1) + err(1, "fchroot"); + } else { + if (capsicate) + errx(1, "-C requires -d"); + if (chdir(argv[0]) == -1) + err(1, "%s", argv[0]); + if (chroot(".") == -1) + err(1, "chroot"); + } if (gids && setgroups(gids, gidlist) == -1) err(1, "setgroups"); @@ -178,7 +209,8 @@ static void usage(void) { - (void)fprintf(stderr, "usage: chroot [-g group] [-G group,group,...] " + (void)fprintf(stderr, + "usage: chroot [-C] [-d] [-g group] [-G group,group,...] " "[-u user] [-n] newroot [command]\n"); exit(1); }