# # WARNING WARNING # # This is a hack and has not been audited for holes etc. It likely doesn't # play well with jails. It's only intended for a 1-shot use. # # Usage: # syscall(218, "/home/buildtools") /* before chroot */ # chroot("/home/buildroot") ... # execv( ... ) # # The idea is that you have binaries, libraries etc in a readonly # location like /home/buildtools/bin, /home/buildtools/lib etc # and then when you chroot to a partial buildjail, failing absolute # paths will be retried against the altroot. # eg: if there is no /home/buildroot/bin/sh, but there is one in # /home/buildtools/bin/sh, then an "exec /bin/sh" inside the chroot # will resolve to the one in the altroot. # # CAVEAT: the intent is not to hide the contents of the altroot - # you can cd to /bin in this example and do a ls. It is merely # to avoid having to copy the contents of /home/buildtools into # /home/buildjail. # # Questions: peter@freebsd.org # TODO: # libc hooks and userland visibility # use a real syscall number - we use a fixed one at work # Index: compat/freebsd32/freebsd32_syscall.h =================================================================== --- compat/freebsd32/freebsd32_syscall.h (revision 264842) +++ compat/freebsd32/freebsd32_syscall.h (working copy) @@ -192,6 +192,7 @@ #define FREEBSD32_SYS_freebsd32_futimes 206 #define FREEBSD32_SYS_getpgid 207 #define FREEBSD32_SYS_poll 209 +#define FREEBSD32_SYS_alt_chroot 218 #define FREEBSD32_SYS_freebsd7_freebsd32_semctl 220 #define FREEBSD32_SYS_semget 221 #define FREEBSD32_SYS_semop 222 Index: compat/freebsd32/freebsd32_syscalls.c =================================================================== --- compat/freebsd32/freebsd32_syscalls.c (revision 264842) +++ compat/freebsd32/freebsd32_syscalls.c (working copy) @@ -228,7 +228,7 @@ "lkmnosys", /* 215 = lkmnosys */ "lkmnosys", /* 216 = lkmnosys */ "lkmnosys", /* 217 = lkmnosys */ - "lkmnosys", /* 218 = lkmnosys */ + "alt_chroot", /* 218 = alt_chroot */ "lkmnosys", /* 219 = lkmnosys */ "compat7.freebsd32_semctl", /* 220 = freebsd7 freebsd32_semctl */ "semget", /* 221 = semget */ Index: compat/freebsd32/freebsd32_sysent.c =================================================================== --- compat/freebsd32/freebsd32_sysent.c (revision 264842) +++ compat/freebsd32/freebsd32_sysent.c (working copy) @@ -265,7 +265,7 @@ { AS(nosys_args), (sy_call_t *)lkmnosys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 215 = lkmnosys */ { AS(nosys_args), (sy_call_t *)lkmnosys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 216 = lkmnosys */ { AS(nosys_args), (sy_call_t *)lkmnosys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 217 = lkmnosys */ - { AS(nosys_args), (sy_call_t *)lkmnosys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 218 = lkmnosys */ + { AS(alt_chroot_args), (sy_call_t *)sys_alt_chroot, AUE_NULL, NULL, 0, 0, 0, SY_THR_STATIC }, /* 218 = alt_chroot */ { AS(nosys_args), (sy_call_t *)lkmnosys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 219 = lkmnosys */ { 0, (sy_call_t *)lkmressys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 220 = freebsd7 freebsd32_semctl */ { AS(semget_args), (sy_call_t *)lkmressys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 221 = semget */ Index: compat/freebsd32/freebsd32_systrace_args.c =================================================================== --- compat/freebsd32/freebsd32_systrace_args.c (revision 264842) +++ compat/freebsd32/freebsd32_systrace_args.c (working copy) @@ -1089,9 +1089,11 @@ *n_args = 0; break; } - /* lkmnosys */ + /* alt_chroot */ case 218: { - *n_args = 0; + struct alt_chroot_args *p = params; + uarg[0] = (intptr_t) p->path; /* char * */ + *n_args = 1; break; } /* lkmnosys */ @@ -5007,8 +5009,15 @@ /* lkmnosys */ case 217: break; - /* lkmnosys */ + /* alt_chroot */ case 218: + switch(ndx) { + case 0: + p = "char *"; + break; + default: + break; + }; break; /* lkmnosys */ case 219: @@ -9474,8 +9483,11 @@ case 216: /* lkmnosys */ case 217: - /* lkmnosys */ + /* alt_chroot */ case 218: + if (ndx == 0 || ndx == 1) + p = "int"; + break; /* lkmnosys */ case 219: /* semget */ Index: compat/freebsd32/syscalls.master =================================================================== --- compat/freebsd32/syscalls.master (revision 264842) +++ compat/freebsd32/syscalls.master (working copy) @@ -403,7 +403,7 @@ 215 AUE_NULL NODEF|NOTSTATIC lkmnosys lkmnosys nosys_args int 216 AUE_NULL NODEF|NOTSTATIC lkmnosys lkmnosys nosys_args int 217 AUE_NULL NODEF|NOTSTATIC lkmnosys lkmnosys nosys_args int -218 AUE_NULL NODEF|NOTSTATIC lkmnosys lkmnosys nosys_args int +218 AUE_NULL NOPROTO { int alt_chroot(char *path); } 219 AUE_NULL NODEF|NOTSTATIC lkmnosys lkmnosys nosys_args int ; Index: kern/init_sysent.c =================================================================== --- kern/init_sysent.c (revision 264842) +++ kern/init_sysent.c (working copy) @@ -252,7 +252,7 @@ { AS(nosys_args), (sy_call_t *)lkmnosys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 215 = lkmnosys */ { AS(nosys_args), (sy_call_t *)lkmnosys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 216 = lkmnosys */ { AS(nosys_args), (sy_call_t *)lkmnosys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 217 = lkmnosys */ - { AS(nosys_args), (sy_call_t *)lkmnosys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 218 = lkmnosys */ + { AS(alt_chroot_args), (sy_call_t *)sys_alt_chroot, AUE_NULL, NULL, 0, 0, 0, SY_THR_STATIC }, /* 218 = alt_chroot */ { AS(nosys_args), (sy_call_t *)lkmnosys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 219 = lkmnosys */ { 0, (sy_call_t *)lkmressys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 220 = freebsd7 __semctl */ { AS(semget_args), (sy_call_t *)lkmressys, AUE_NULL, NULL, 0, 0, 0, SY_THR_ABSENT }, /* 221 = semget */ Index: kern/kern_descrip.c =================================================================== --- kern/kern_descrip.c (revision 264842) +++ kern/kern_descrip.c (working copy) @@ -1786,6 +1786,9 @@ newfdp->fd_fd.fd_jdir = fdp->fd_jdir; if (newfdp->fd_fd.fd_jdir) VREF(newfdp->fd_fd.fd_jdir); + newfdp->fd_fd.fd_ardir = fdp->fd_ardir; + if (newfdp->fd_fd.fd_ardir) + VREF(newfdp->fd_fd.fd_ardir); FILEDESC_SUNLOCK(fdp); } @@ -1931,7 +1934,7 @@ int i; struct filedesc_to_leader *fdtol; struct file *fp; - struct vnode *cdir, *jdir, *rdir, *vp; + struct vnode *cdir, *jdir, *rdir, *ardir, *vp; struct flock lf; /* Certain daemons might not have file descriptors. */ @@ -2043,6 +2046,8 @@ fdp->fd_rdir = NULL; jdir = fdp->fd_jdir; fdp->fd_jdir = NULL; + ardir = fdp->fd_ardir; + fdp->fd_ardir = NULL; FILEDESC_XUNLOCK(fdp); if (cdir != NULL) @@ -2051,6 +2056,8 @@ vrele(rdir); if (jdir != NULL) vrele(jdir); + if (ardir) + vrele(ardir); fddrop(fdp); } @@ -2808,6 +2815,11 @@ fdp->fd_jdir = newdp; nrele++; } + if (fdp->fd_ardir == olddp) { + vref(newdp); + fdp->fd_ardir = newdp; + nrele++; + } FILEDESC_XUNLOCK(fdp); fddrop(fdp); } Index: kern/syscalls.c =================================================================== --- kern/syscalls.c (revision 264842) +++ kern/syscalls.c (working copy) @@ -225,7 +225,7 @@ "lkmnosys", /* 215 = lkmnosys */ "lkmnosys", /* 216 = lkmnosys */ "lkmnosys", /* 217 = lkmnosys */ - "lkmnosys", /* 218 = lkmnosys */ + "alt_chroot", /* 218 = alt_chroot */ "lkmnosys", /* 219 = lkmnosys */ "compat7.__semctl", /* 220 = freebsd7 __semctl */ "semget", /* 221 = semget */ Index: kern/syscalls.master =================================================================== --- kern/syscalls.master (revision 264842) +++ kern/syscalls.master (working copy) @@ -410,7 +410,7 @@ 215 AUE_NULL NODEF|NOTSTATIC lkmnosys lkmnosys nosys_args int 216 AUE_NULL NODEF|NOTSTATIC lkmnosys lkmnosys nosys_args int 217 AUE_NULL NODEF|NOTSTATIC lkmnosys lkmnosys nosys_args int -218 AUE_NULL NODEF|NOTSTATIC lkmnosys lkmnosys nosys_args int +218 AUE_NULL STD { int alt_chroot(char *path); } 219 AUE_NULL NODEF|NOTSTATIC lkmnosys lkmnosys nosys_args int ; Index: kern/systrace_args.c =================================================================== --- kern/systrace_args.c (revision 264842) +++ kern/systrace_args.c (working copy) @@ -1182,9 +1182,11 @@ *n_args = 0; break; } - /* lkmnosys */ + /* alt_chroot */ case 218: { - *n_args = 0; + struct alt_chroot_args *p = params; + uarg[0] = (intptr_t) p->path; /* char * */ + *n_args = 1; break; } /* lkmnosys */ @@ -5255,8 +5257,15 @@ /* lkmnosys */ case 217: break; - /* lkmnosys */ + /* alt_chroot */ case 218: + switch(ndx) { + case 0: + p = "char *"; + break; + default: + break; + }; break; /* lkmnosys */ case 219: @@ -9667,8 +9676,11 @@ case 216: /* lkmnosys */ case 217: - /* lkmnosys */ + /* alt_chroot */ case 218: + if (ndx == 0 || ndx == 1) + p = "int"; + break; /* lkmnosys */ case 219: /* semget */ Index: kern/vfs_cache.c =================================================================== --- kern/vfs_cache.c (revision 264842) +++ kern/vfs_cache.c (working copy) @@ -309,6 +309,7 @@ static int vn_vptocnp_locked(struct vnode **vp, struct ucred *cred, char *buf, u_int *buflen); static int vn_fullpath1(struct thread *td, struct vnode *vp, struct vnode *rdir, + struct vnode *ardir, char *buf, char **retbuf, u_int buflen); static MALLOC_DEFINE(M_VFSCACHE, "vfscache", "VFS name cache entries"); @@ -1073,7 +1074,7 @@ { char *bp, *tmpbuf; struct filedesc *fdp; - struct vnode *cdir, *rdir; + struct vnode *cdir, *rdir, *ardir; int error; if (disablecwd) @@ -1090,8 +1091,13 @@ VREF(cdir); rdir = fdp->fd_rdir; VREF(rdir); + ardir = fdp->fd_ardir; + if (ardir) + VREF(ardir); FILEDESC_SUNLOCK(fdp); - error = vn_fullpath1(td, cdir, rdir, tmpbuf, &bp, buflen); + error = vn_fullpath1(td, cdir, rdir, ardir, tmpbuf, &bp, buflen); + if (ardir) + vrele(ardir); vrele(rdir); vrele(cdir); @@ -1139,7 +1145,7 @@ { char *buf; struct filedesc *fdp; - struct vnode *rdir; + struct vnode *rdir, *ardir; int error; if (disablefullpath) @@ -1152,8 +1158,13 @@ FILEDESC_SLOCK(fdp); rdir = fdp->fd_rdir; VREF(rdir); + ardir = fdp->fd_ardir; + if (ardir) + VREF(ardir); FILEDESC_SUNLOCK(fdp); - error = vn_fullpath1(td, vn, rdir, buf, retbuf, MAXPATHLEN); + error = vn_fullpath1(td, vn, rdir, ardir, buf, retbuf, MAXPATHLEN); + if (ardir) + vrele(ardir); vrele(rdir); if (!error) @@ -1181,7 +1192,7 @@ if (vn == NULL) return (EINVAL); buf = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); - error = vn_fullpath1(td, vn, rootvnode, buf, retbuf, MAXPATHLEN); + error = vn_fullpath1(td, vn, rootvnode, NULL, buf, retbuf, MAXPATHLEN); if (!error) *freebuf = buf; else @@ -1271,6 +1282,7 @@ */ static int vn_fullpath1(struct thread *td, struct vnode *vp, struct vnode *rdir, + struct vnode *ardir, char *buf, char **retbuf, u_int buflen) { int error, slash_prefixed; @@ -1300,7 +1312,9 @@ buf[--buflen] = '/'; slash_prefixed = 1; } - while (vp != rdir && vp != rootvnode) { + if (ardir == NULL) + ardir = rdir; + while (vp != rdir && vp != rootvnode && vp != ardir) { if (vp->v_vflag & VV_ROOT) { if (vp->v_iflag & VI_DOOMED) { /* forced unmount */ CACHE_RUNLOCK(); Index: kern/vfs_lookup.c =================================================================== --- kern/vfs_lookup.c (revision 264842) +++ kern/vfs_lookup.c (working copy) @@ -132,6 +132,9 @@ struct componentname *cnp = &ndp->ni_cnd; struct thread *td = cnp->cn_thread; struct proc *p = td->td_proc; + int altroot_available = 0; + int altroot_try = 0; + int altroot_pathlen; ndp->ni_cnd.cn_cred = ndp->ni_cnd.cn_thread->td_ucred; KASSERT(cnp->cn_cred && p, ("namei: bad cred/proc")); @@ -205,6 +208,7 @@ */ FILEDESC_SLOCK(fdp); ndp->ni_rootdir = fdp->fd_rdir; + ndp->ni_arootdir = fdp->fd_ardir; ndp->ni_topdir = fdp->fd_jdir; /* @@ -272,6 +276,9 @@ } SDT_PROBE(vfs, namei, lookup, entry, dp, cnp->cn_pnbuf, cnp->cn_flags, 0, 0); + + if (ndp->ni_arootdir != NULL && ndp->ni_arootdir != ndp->ni_rootdir) + altroot_available = 1; for (;;) { /* * Check if root directory should replace current directory. @@ -278,6 +285,7 @@ * Done at start of translation and after symbolic link. */ cnp->cn_nameptr = cnp->cn_pnbuf; + altroot_pathlen = ndp->ni_pathlen; if (*(cnp->cn_nameptr) == '/') { vrele(dp); if (ndp->ni_strictrelative != 0) { @@ -291,12 +299,26 @@ cnp->cn_nameptr++; ndp->ni_pathlen--; } - dp = ndp->ni_rootdir; + if (altroot_try == 1) + dp = ndp->ni_arootdir; + else + dp = ndp->ni_rootdir; VREF(dp); } ndp->ni_startdir = dp; error = lookup(ndp); if (error) { + if (error == ENOENT && cnp->cn_pnbuf[0] == '/' && altroot_available && altroot_try == 0) { + /* Reset and start again from the top */ + altroot_try = 1; + ndp->ni_next = cnp->cn_nameptr; + ndp->ni_pathlen = altroot_pathlen; + FILEDESC_SLOCK(fdp); + dp = fdp->fd_cdir; /* Reacquire cwd, which we will drop after continue */ + VREF(dp); + FILEDESC_SUNLOCK(fdp); + continue; + } uma_zfree(namei_zone, cnp->cn_pnbuf); #ifdef DIAGNOSTIC cnp->cn_pnbuf = NULL; @@ -306,6 +328,7 @@ 0, 0); return (error); } + altroot_try = 0; /* * If not a symbolic link, we're done. */ @@ -323,7 +346,7 @@ 0, 0, 0); return (0); } - if (ndp->ni_loopcnt++ >= MAXSYMLINKS) { + if (ndp->ni_loopcnt++ > MAXSYMLINKS) { error = ELOOP; break; } @@ -650,6 +673,7 @@ if (dp == pr->pr_root) break; if (dp == ndp->ni_rootdir || + dp == ndp->ni_arootdir || dp == ndp->ni_topdir || dp == rootvnode || pr != NULL || Index: kern/vfs_syscalls.c =================================================================== --- kern/vfs_syscalls.c (revision 264842) +++ kern/vfs_syscalls.c (working copy) @@ -996,6 +996,58 @@ cap_rights_set(rightsp, CAP_FLOCK); } +int +sys_alt_chroot(struct thread *td, struct alt_chroot_args *uap) +{ + int error; + struct nameidata nd; + + error = priv_check(td, PRIV_VFS_CHROOT); + if (error) + return (error); + if (jailed(td->td_ucred) || td->td_proc->p_fd->fd_ardir != NULL) + return (EPERM); + NDINIT(&nd, LOOKUP, FOLLOW | LOCKSHARED | LOCKLEAF | + AUDITVNODE1, UIO_USERSPACE, uap->path, td); + error = namei(&nd); + if (error) + goto error; +#ifdef MAC + if ((error = mac_vnode_check_chroot(td->td_ucred, nd.ni_vp))) + goto e_vunlock; +#endif + VOP_UNLOCK(nd.ni_vp, 0); + error = change_altroot(nd.ni_vp, td); + vrele(nd.ni_vp); + NDFREE(&nd, NDF_ONLY_PNBUF); + return (error); +#ifdef MAC +e_vunlock: + vput(nd.ni_vp); +#endif +error: + NDFREE(&nd, NDF_ONLY_PNBUF); + return (error); +} + +int +change_altroot(struct vnode *vp, struct thread *td) +{ + struct filedesc *fdp; + struct vnode *oldvp; + + fdp = td->td_proc->p_fd; + FILEDESC_XLOCK(fdp); + oldvp = fdp->fd_ardir; + if (vp) + VREF(vp); + fdp->fd_ardir = vp; + FILEDESC_XUNLOCK(fdp); + if (oldvp) + vrele(oldvp); + return (0); +} + /* * Check permissions, allocate an open file structure, and call the device * open routine if any. Index: sys/filedesc.h =================================================================== --- sys/filedesc.h (revision 264842) +++ sys/filedesc.h (working copy) @@ -70,6 +70,7 @@ struct vnode *fd_cdir; /* current directory */ struct vnode *fd_rdir; /* root directory */ struct vnode *fd_jdir; /* jail root directory */ + struct vnode *fd_ardir; /* alt-root directory */ int fd_nfiles; /* number of open files allocated */ NDSLOTTYPE *fd_map; /* bitmap of free fds */ int fd_lastfile; /* high-water mark of fd_ofiles */ Index: sys/namei.h =================================================================== --- sys/namei.h (revision 264842) +++ sys/namei.h (working copy) @@ -72,6 +72,7 @@ struct vnode *ni_startdir; /* starting directory */ struct vnode *ni_rootdir; /* logical root directory */ struct vnode *ni_topdir; /* logical top directory */ + struct vnode *ni_arootdir; /* alternate root directory */ int ni_dirfd; /* starting directory for *at functions */ int ni_strictrelative; /* relative lookup only; no '..' */ /* Index: sys/syscall.h =================================================================== --- sys/syscall.h (revision 264842) +++ sys/syscall.h (working copy) @@ -196,6 +196,7 @@ #define SYS_futimes 206 #define SYS_getpgid 207 #define SYS_poll 209 +#define SYS_alt_chroot 218 #define SYS_freebsd7___semctl 220 #define SYS_semget 221 #define SYS_semop 222 Index: sys/syscall.mk =================================================================== --- sys/syscall.mk (revision 264842) +++ sys/syscall.mk (working copy) @@ -148,6 +148,7 @@ futimes.o \ getpgid.o \ poll.o \ + alt_chroot.o \ freebsd7___semctl.o \ semget.o \ semop.o \ Index: sys/sysproto.h =================================================================== --- sys/sysproto.h (revision 264842) +++ sys/sysproto.h (working copy) @@ -651,6 +651,9 @@ char nfds_l_[PADL_(u_int)]; u_int nfds; char nfds_r_[PADR_(u_int)]; char timeout_l_[PADL_(int)]; int timeout; char timeout_r_[PADR_(int)]; }; +struct alt_chroot_args { + char path_l_[PADL_(char *)]; char * path; char path_r_[PADR_(char *)]; +}; struct semget_args { char key_l_[PADL_(key_t)]; key_t key; char key_r_[PADR_(key_t)]; char nsems_l_[PADL_(int)]; int nsems; char nsems_r_[PADR_(int)]; @@ -1951,6 +1954,7 @@ int sys_futimes(struct thread *, struct futimes_args *); int sys_getpgid(struct thread *, struct getpgid_args *); int sys_poll(struct thread *, struct poll_args *); +int sys_alt_chroot(struct thread *, struct alt_chroot_args *); int sys_semget(struct thread *, struct semget_args *); int sys_semop(struct thread *, struct semop_args *); int sys_msgget(struct thread *, struct msgget_args *); @@ -2649,6 +2653,7 @@ #define SYS_AUE_futimes AUE_FUTIMES #define SYS_AUE_getpgid AUE_GETPGID #define SYS_AUE_poll AUE_POLL +#define SYS_AUE_alt_chroot AUE_NULL #define SYS_AUE_freebsd7___semctl AUE_SEMCTL #define SYS_AUE_semget AUE_SEMGET #define SYS_AUE_semop AUE_SEMOP Index: sys/vnode.h =================================================================== --- sys/vnode.h (revision 264842) +++ sys/vnode.h (working copy) @@ -606,6 +606,7 @@ void cache_purgevfs(struct mount *mp); int change_dir(struct vnode *vp, struct thread *td); int change_root(struct vnode *vp, struct thread *td); +int change_altroot(struct vnode *vp, struct thread *td); void cvtstat(struct stat *st, struct ostat *ost); void cvtnstat(struct stat *sb, struct nstat *nsb); int getnewvnode(const char *tag, struct mount *mp, struct vop_vector *vops,