diff --git a/include/unistd.h b/include/unistd.h --- a/include/unistd.h +++ b/include/unistd.h @@ -468,6 +468,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/libc/sys/Makefile.inc b/lib/libc/sys/Makefile.inc --- a/lib/libc/sys/Makefile.inc +++ b/lib/libc/sys/Makefile.inc @@ -377,6 +377,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/libc/sys/Symbol.map b/lib/libc/sys/Symbol.map --- a/lib/libc/sys/Symbol.map +++ b/lib/libc/sys/Symbol.map @@ -417,6 +417,7 @@ FBSD_1.7 { _Fork; + fchroot; fspacectl; kqueuex; membarrier; diff --git a/lib/libc/sys/chroot.2 b/lib/libc/sys/chroot.2 --- a/lib/libc/sys/chroot.2 +++ b/lib/libc/sys/chroot.2 @@ -31,7 +31,8 @@ .Dt CHROOT 2 .Os .Sh NAME -.Nm chroot +.Nm chroot , +.Nm fchroot .Nd change root directory .Sh LIBRARY .Lb libc @@ -39,6 +40,8 @@ .In unistd.h .Ft int .Fn chroot "const char *dirname" +.Ft int +.Fn fchroot "int fd" .Sh DESCRIPTION The .Fa dirname @@ -94,6 +97,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/sys/compat/freebsd32/freebsd32_syscall.h b/sys/compat/freebsd32/freebsd32_syscall.h --- a/sys/compat/freebsd32/freebsd32_syscall.h +++ b/sys/compat/freebsd32/freebsd32_syscall.h @@ -502,4 +502,5 @@ #define FREEBSD32_SYS_swapoff 582 #define FREEBSD32_SYS_kqueuex 583 #define FREEBSD32_SYS_membarrier 584 -#define FREEBSD32_SYS_MAXSYSCALL 585 +#define FREEBSD32_SYS_fchroot 585 +#define FREEBSD32_SYS_MAXSYSCALL 586 diff --git a/sys/compat/freebsd32/freebsd32_syscalls.c b/sys/compat/freebsd32/freebsd32_syscalls.c --- a/sys/compat/freebsd32/freebsd32_syscalls.c +++ b/sys/compat/freebsd32/freebsd32_syscalls.c @@ -590,4 +590,5 @@ "swapoff", /* 582 = swapoff */ "kqueuex", /* 583 = kqueuex */ "membarrier", /* 584 = membarrier */ + "fchroot", /* 585 = fchroot */ }; 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 @@ -646,4 +646,5 @@ { .sy_narg = AS(swapoff_args), .sy_call = (sy_call_t *)sys_swapoff, .sy_auevent = AUE_SWAPOFF, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 582 = swapoff */ { .sy_narg = AS(kqueuex_args), .sy_call = (sy_call_t *)sys_kqueuex, .sy_auevent = AUE_KQUEUE, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 583 = kqueuex */ { .sy_narg = AS(membarrier_args), .sy_call = (sy_call_t *)sys_membarrier, .sy_auevent = AUE_NULL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 584 = membarrier */ + { .sy_narg = AS(fchroot_args), .sy_call = (sy_call_t *)sys_fchroot, .sy_auevent = AUE_NULL, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 585 = fchroot */ }; diff --git a/sys/compat/freebsd32/freebsd32_systrace_args.c b/sys/compat/freebsd32/freebsd32_systrace_args.c --- a/sys/compat/freebsd32/freebsd32_systrace_args.c +++ b/sys/compat/freebsd32/freebsd32_systrace_args.c @@ -3336,6 +3336,13 @@ *n_args = 3; break; } + /* fchroot */ + case 585: { + struct fchroot_args *p = params; + iarg[a++] = p->fd; /* int */ + *n_args = 1; + break; + } default: *n_args = 0; break; @@ -9005,6 +9012,16 @@ break; }; break; + /* fchroot */ + case 585: + switch (ndx) { + case 0: + p = "int"; + break; + default: + break; + }; + break; default: break; }; @@ -10873,6 +10890,11 @@ if (ndx == 0 || ndx == 1) p = "int"; break; + /* fchroot */ + case 585: + if (ndx == 0 || ndx == 1) + p = "int"; + break; default: break; }; 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 @@ -645,4 +645,5 @@ { .sy_narg = AS(swapoff_args), .sy_call = (sy_call_t *)sys_swapoff, .sy_auevent = AUE_SWAPOFF, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 582 = swapoff */ { .sy_narg = AS(kqueuex_args), .sy_call = (sy_call_t *)sys_kqueuex, .sy_auevent = AUE_KQUEUE, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 583 = kqueuex */ { .sy_narg = AS(membarrier_args), .sy_call = (sy_call_t *)sys_membarrier, .sy_auevent = AUE_NULL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 584 = membarrier */ + { .sy_narg = AS(fchroot_args), .sy_call = (sy_call_t *)sys_fchroot, .sy_auevent = AUE_NULL, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 585 = fchroot */ }; 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 @@ -59,6 +59,7 @@ __read_mostly cap_rights_t cap_fchflags_rights; __read_mostly cap_rights_t cap_fchmod_rights; __read_mostly cap_rights_t cap_fchown_rights; +__read_mostly cap_rights_t cap_fchroot_rights; __read_mostly cap_rights_t cap_fcntl_rights; __read_mostly cap_rights_t cap_fexecve_rights; __read_mostly cap_rights_t cap_flock_rights; @@ -108,6 +109,7 @@ 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); + cap_rights_init_one(&cap_fchroot_rights, CAP_FCHROOT); cap_rights_init_one(&cap_fcntl_rights, CAP_FCNTL); cap_rights_init_one(&cap_fexecve_rights, CAP_FEXECVE); cap_rights_init_one(&cap_flock_rights, CAP_FLOCK); diff --git a/sys/kern/syscalls.c b/sys/kern/syscalls.c --- a/sys/kern/syscalls.c +++ b/sys/kern/syscalls.c @@ -590,4 +590,5 @@ "swapoff", /* 582 = swapoff */ "kqueuex", /* 583 = kqueuex */ "membarrier", /* 584 = membarrier */ + "fchroot", /* 585 = fchroot */ }; diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master --- a/sys/kern/syscalls.master +++ b/sys/kern/syscalls.master @@ -3318,6 +3318,10 @@ int cpu_id ); } - +585 AUE_NULL STD { + int fchroot( + int fd, + ); + } ; vim: syntax=off diff --git a/sys/kern/systrace_args.c b/sys/kern/systrace_args.c --- a/sys/kern/systrace_args.c +++ b/sys/kern/systrace_args.c @@ -3432,6 +3432,13 @@ *n_args = 3; break; } + /* fchroot */ + case 585: { + struct fchroot_args *p = params; + iarg[a++] = p->fd; /* int */ + *n_args = 1; + break; + } default: *n_args = 0; break; @@ -9175,6 +9182,16 @@ break; }; break; + /* fchroot */ + case 585: + switch (ndx) { + case 0: + p = "int"; + break; + default: + break; + }; + break; default: break; }; @@ -11138,6 +11155,11 @@ if (ndx == 0 || ndx == 1) p = "int"; break; + /* fchroot */ + case 585: + if (ndx == 0 || ndx == 1) + p = "int"; + break; default: break; }; 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 @@ -968,18 +968,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; @@ -990,30 +985,67 @@ 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); +} + +int +sys_fchroot(struct thread *td, struct fchroot_args *uap) +{ + struct vnode *vp; + struct file *fp; + int error; + + error = getvnode_path(td, uap->fd, &cap_fchroot_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 @@ -66,6 +66,7 @@ extern cap_rights_t cap_fchflags_rights; extern cap_rights_t cap_fchmod_rights; extern cap_rights_t cap_fchown_rights; +extern cap_rights_t cap_fchroot_rights; extern cap_rights_t cap_fcntl_rights; extern cap_rights_t cap_fexecve_rights; extern cap_rights_t cap_flock_rights; diff --git a/sys/sys/capsicum.h b/sys/sys/capsicum.h --- a/sys/sys/capsicum.h +++ b/sys/sys/capsicum.h @@ -202,6 +202,9 @@ /* Allows for renameat(2) (target directory descriptor). */ #define CAP_RENAMEAT_TARGET (CAP_LOOKUP | 0x0000040000000000ULL) +/* Allows for fchroot(2). */ +#define CAP_FCHROOT CAPRIGHT(0, 0x0000080000000000ULL) + #define CAP_SOCK_CLIENT \ (CAP_CONNECT | CAP_GETPEERNAME | CAP_GETSOCKNAME | CAP_GETSOCKOPT | \ CAP_PEELOFF | CAP_RECV | CAP_SEND | CAP_SETSOCKOPT | CAP_SHUTDOWN) @@ -211,11 +214,9 @@ CAP_SETSOCKOPT | CAP_SHUTDOWN) /* All used bits for index 0. */ -#define CAP_ALL0 CAPRIGHT(0, 0x000007FFFFFFFFFFULL) +#define CAP_ALL0 CAPRIGHT(0, 0x00000FFFFFFFFFFFULL) /* Available bits for index 0. */ -#define CAP_UNUSED0_44 CAPRIGHT(0, 0x0000080000000000ULL) -/* ... */ #define CAP_UNUSED0_57 CAPRIGHT(0, 0x0100000000000000ULL) /* INDEX 1 */ diff --git a/sys/sys/syscall.h b/sys/sys/syscall.h --- a/sys/sys/syscall.h +++ b/sys/sys/syscall.h @@ -521,4 +521,5 @@ #define SYS_swapoff 582 #define SYS_kqueuex 583 #define SYS_membarrier 584 -#define SYS_MAXSYSCALL 585 +#define SYS_fchroot 585 +#define SYS_MAXSYSCALL 586 diff --git a/sys/sys/syscall.mk b/sys/sys/syscall.mk --- a/sys/sys/syscall.mk +++ b/sys/sys/syscall.mk @@ -426,4 +426,5 @@ sched_getcpu.o \ swapoff.o \ kqueuex.o \ - membarrier.o + membarrier.o \ + fchroot.o diff --git a/sys/sys/sysproto.h b/sys/sys/sysproto.h --- a/sys/sys/sysproto.h +++ b/sys/sys/sysproto.h @@ -1862,6 +1862,9 @@ char flags_l_[PADL_(unsigned)]; unsigned flags; char flags_r_[PADR_(unsigned)]; char cpu_id_l_[PADL_(int)]; int cpu_id; char cpu_id_r_[PADR_(int)]; }; +struct fchroot_args { + char fd_l_[PADL_(int)]; int fd; char fd_r_[PADR_(int)]; +}; int sys_exit(struct thread *, struct exit_args *); int sys_fork(struct thread *, struct fork_args *); int sys_read(struct thread *, struct read_args *); @@ -2259,6 +2262,7 @@ int sys_swapoff(struct thread *, struct swapoff_args *); int sys_kqueuex(struct thread *, struct kqueuex_args *); int sys_membarrier(struct thread *, struct membarrier_args *); +int sys_fchroot(struct thread *, struct fchroot_args *); #ifdef COMPAT_43 @@ -3230,6 +3234,7 @@ #define SYS_AUE_swapoff AUE_SWAPOFF #define SYS_AUE_kqueuex AUE_KQUEUE #define SYS_AUE_membarrier AUE_NULL +#define SYS_AUE_fchroot AUE_NULL #undef PAD_ #undef PADL_ 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 @@ -35,6 +35,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 @@ -53,6 +54,10 @@ .Pp The options are as follows: .Bl -tag -width "-G group[,group ...]" +.It Fl d +Assume the +.Ar newroot +is a file descriptor number and not a path. .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 @@ -46,6 +46,7 @@ #include #include +#include #include #include #include @@ -67,16 +68,19 @@ const char *shell; gid_t gid, *gidlist; uid_t uid; - int arg, ch, error, gids; + int arg, ch, error, fd, gids; long ngroups_max; - bool nonprivileged; + bool nonprivileged, fd_not_path; gid = 0; uid = 0; user = group = grouplist = NULL; - nonprivileged = false; - while ((ch = getopt(argc, argv, "G:g:u:n")) != -1) { + nonprivileged = fd_not_path = false; + while ((ch = getopt(argc, argv, "dG:g:u:n")) != -1) { switch(ch) { + case 'd': + fd_not_path = true; + break; case 'u': user = optarg; if (*user == '\0') @@ -165,8 +169,20 @@ err(1, "procctl"); } - if (chdir(argv[0]) == -1 || chroot(".") == -1) - err(1, "%s", argv[0]); + if (fd_not_path) { + fd = (uid_t)strtoul(argv[0], &endp, 0); + if (*endp != '\0') + errx(1, "%s is not a number", argv[0]); + if (fchdir(fd) == -1) + err(1, "%s", argv[0]); + if (fchroot(fd) == -1) + err(1, "chroot"); + } else { + 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"); @@ -190,7 +206,8 @@ static void usage(void) { - (void)fprintf(stderr, "usage: chroot [-g group] [-G group,group,...] " + (void)fprintf(stderr, + "usage: chroot [-d] [-g group] [-G group,group,...] " "[-u user] [-n] newroot [command]\n"); exit(1); }