diff --git a/xbps-src/Makefile b/xbps-src/Makefile index f6a1bc72c0..01209b5e79 100644 --- a/xbps-src/Makefile +++ b/xbps-src/Makefile @@ -10,6 +10,7 @@ all: -e "s|@@XBPS_INSTALL_ETCDIR@@|$(ETCDIR)|g" \ -e "s|@@XBPS_INSTALL_SHAREDIR@@|$(SHAREDIR)|g" \ -e "s|@@XBPS_INSTALL_SBINDIR@@|$(SBINDIR)|g" \ + -e "s|@@XBPS_INSTALL_LIBEXECDIR@@|$(LIBEXECDIR)|g" \ $$bin.sh.in > $$bin; \ done for dir in $(SUBDIRS); do \ diff --git a/xbps-src/etc/xbps-src.conf.in b/xbps-src/etc/xbps-src.conf.in index b73bad0bf6..4a5bb9f0f0 100644 --- a/xbps-src/etc/xbps-src.conf.in +++ b/xbps-src/etc/xbps-src.conf.in @@ -44,19 +44,10 @@ XBPS_COMPRESS_CMD=xz #XBPS_PREFER_BINPKG_DEPS=yes # -# Build packages with your unprivileged user in the chroot -# via capchroot. The only required steps with privileges are -# the bind mounts, a helper script (xbps-src-chroot-helper) needs -# to be run with sudo for this task. +# Build packages with your unprivileged user in the chroot thanks +# to POSIX.1e Capabilities as explained in capabilities(7) on GNU/Linux. # # fakeroot is only used for the installation stage via the helper # script xbps-src-doinst-helper. # -# capchroot allows ordinary users to use the chroot(2) syscall. -# To make this work, uncomment this option and run the following -# commands (as root): -# -# $ setcap cap_sys_chroot=ep /usr/bin/capchroot -# $ echo "/path/to/masterdir $(whoami)" >> /etc/capchroot.allow -# #XBPS_USE_CAPCHROOT=yes diff --git a/xbps-src/libexec/Makefile b/xbps-src/libexec/Makefile index 1fa128778f..7ac55012b4 100644 --- a/xbps-src/libexec/Makefile +++ b/xbps-src/libexec/Makefile @@ -1,30 +1,58 @@ include ../vars.mk -BINS = xbps-src-chroot-helper xbps-src-doinst-helper +SH_BINS = xbps-src-chroot-helper xbps-src-doinst-helper +MOUNT_BIN = xbps-src-chroot-capmount +UMOUNT_BIN = xbps-src-chroot-capumount +CHROOT_BIN = xbps-src-capchroot +BINS = $(CHROOT_BIN) $(MOUNT_BIN) $(UMOUNT_BIN) +WFLAGS = -Wall -Werror +LDFLAGS = -lcap + +ifdef IN_CHROOT +BINS = +endif .PHONY: all -all: - for bin in $(BINS); do \ +all: $(BINS) + for bin in $(SH_BINS); do \ sed -e "s|@@XBPS_INSTALL_PREFIX@@|$(PREFIX)|g" \ -e "s|@@XBPS_INSTALL_ETCDIR@@|$(ETCDIR)|g" \ -e "s|@@XBPS_INSTALL_SHAREDIR@@|$(SHAREDIR)|g" \ -e "s|@@XBPS_INSTALL_SBINDIR@@|$(SBINDIR)|g" \ + -e "s|@@XBPS_INSTALL_LIBEXECDIR@@|$(LIBEXECDIR)|g" \ $$bin.sh.in > $$bin; \ done .PHONY: clean clean: - -rm -f $(BINS) + -rm -f $(BINS) $(SH_BINS) .PHONY: install install: all install -d $(DESTDIR)$(LIBEXECDIR) - for bin in $(BINS); do \ - install -m 755 $$bin $(DESTDIR)$(LIBEXECDIR); \ + for bin in $(SH_BINS); do \ + install -m755 $$bin $(DESTDIR)$(LIBEXECDIR); \ done +ifdef BINS + install -m755 $(MOUNT_BIN) $(DESTDIR)$(LIBEXECDIR) + setcap cap_sys_admin=ep $(DESTDIR)$(LIBEXECDIR)/$(MOUNT_BIN) + install -m755 $(UMOUNT_BIN) $(DESTDIR)$(LIBEXECDIR) + setcap cap_sys_admin=ep $(DESTDIR)$(LIBEXECDIR)/$(UMOUNT_BIN) + install -m755 $(CHROOT_BIN) $(DESTDIR)$(LIBEXECDIR) + setcap cap_sys_chroot=ep $(DESTDIR)$(LIBEXECDIR)/$(CHROOT_BIN) +endif .PHONY: uninstall uninstall: - for bin in $(BINS); do \ + for bin in $(BINS) $(SH_BINS); do \ rm -f $(DESTDIR)$(LIBEXECDIR)/$$bin; \ done + +$(MOUNT_BIN): + $(CC) $(WFLAGS) $(LDFLAGS) mount.c -o $@ + +$(UMOUNT_BIN): + $(CC) $(WFLAGS) $(LDFLAGS) umount.c -o $@ + +$(CHROOT_BIN): + $(CC) $(WFLAGS) $(LDFLAGS) chroot.c -o $@ diff --git a/xbps-src/libexec/chroot.c b/xbps-src/libexec/chroot.c new file mode 100644 index 0000000000..e332e27ca9 --- /dev/null +++ b/xbps-src/libexec/chroot.c @@ -0,0 +1,97 @@ +/* + * chroot() to target directory by using the CAP_CHROOT + * capability set on the file. + * + * Juan RP - 2010/04/26 - Public Domain. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void +usage(void) +{ + fprintf(stderr, "Usage: xbps-src-capchroot \n"); + exit(EXIT_FAILURE); +} + +int +main(int argc, char **argv) +{ + cap_t cap; + cap_flag_value_t effective, permitted; + struct stat st; + char *path; + + if (argc < 3) + usage(); + + cap = cap_get_proc(); + if (cap == NULL) { + fprintf(stderr, "cap_get_proc() returned %s!\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + + cap_get_flag(cap, CAP_SYS_CHROOT, CAP_EFFECTIVE, &effective); + cap_get_flag(cap, CAP_SYS_CHROOT, CAP_PERMITTED, &permitted); + if ((effective != CAP_SET) && (permitted != CAP_SET)) { + fprintf(stderr, "ERROR: missing 'cap_sys_chroot' capability!\n" + "Please set it with: setcap cap_sys_chroot=ep %s'\n", + argv[0]); + cap_free(cap); + exit(EXIT_FAILURE); + } + cap_free(cap); + + if ((path = realpath(argv[1], NULL)) == NULL) { + fprintf(stderr, "ERROR: realpath() %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + /* Disallow chroot to '/' */ + if (strcmp(path, "/") == 0) { + fprintf(stderr, "ERROR: chroot to / is not allowed!\n"); + exit(EXIT_FAILURE); + } + + /* + * Check that uid/gid owns the dir and has rx perms on the + * new target root and it is a directory. + */ + if (stat(path, &st) == -1) { + fprintf(stderr, "ERROR: stat() on %s: %s\n", + path, strerror(errno)); + exit(EXIT_FAILURE); + } + if (S_ISDIR(st.st_mode) == 0) { + fprintf(stderr, "ERROR: '%s' not a directory!\n", path); + exit(EXIT_FAILURE); + } + if ((st.st_uid != getuid()) && (st.st_gid != getgid()) && + (st.st_mode & (S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP))) { + fprintf(stderr, "ERROR: wrong permissions on %s!\n", path); + exit(EXIT_FAILURE); + } + /* All ok, change root and process argv on the target root dir. */ + if (chroot(path) == -1) { + fprintf(stderr, "ERROR: chroot() on %s: %s\n", argv[1], + strerror(errno)); + exit(EXIT_FAILURE); + } + if (chdir("/") == -1) { + fprintf(stderr, "ERROR: chdir(): %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + argv += 2; + (void)execvp(argv[0], argv); + + exit(EXIT_FAILURE); +} diff --git a/xbps-src/libexec/mount.c b/xbps-src/libexec/mount.c new file mode 100644 index 0000000000..6af66e8fd0 --- /dev/null +++ b/xbps-src/libexec/mount.c @@ -0,0 +1,78 @@ +/* + * Bind mounts a filesystem mountpoint into the target directory, + * by using the CAP_SYS_ADMIN capability set on the file. + * + * Juan RP - 2010/04/26 - Public Domain. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +void +usage(void) +{ + fprintf(stderr, "Usage: xbps-src-capbmount [-w] \n"); + exit(EXIT_FAILURE); +} + +int +main(int argc, char **argv) +{ + cap_t cap; + cap_flag_value_t effective, permitted; + unsigned long flags; + int c, rv; + bool dowrite = false; + + while ((c = getopt(argc, argv, "w")) != -1) { + switch (c) { + case 'w': + dowrite = true; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc != 2) + usage(); + + cap = cap_get_proc(); + if (cap == NULL) { + fprintf(stderr, "cap_get_proc() returned %s!\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + + cap_get_flag(cap, CAP_SYS_ADMIN, CAP_EFFECTIVE, &effective); + cap_get_flag(cap, CAP_SYS_ADMIN, CAP_PERMITTED, &permitted); + if ((effective != CAP_SET) && (permitted != CAP_SET)) { + fprintf(stderr, "E: missing 'cap_sys_admin' capability!\n" + "Please set it with: setcap cap_sys_admin=ep %s'\n", + argv[0]); + cap_free(cap); + exit(EXIT_FAILURE); + } + cap_free(cap); + + flags = MS_BIND; + if (!dowrite) + flags |= MS_RDONLY; + + rv = mount(argv[0], argv[1], "none", flags, NULL); + if (rv != 0) { + fprintf(stderr, "E: cannot mount %s into %s: %s\n", argv[0], + argv[1], strerror(errno)); + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} diff --git a/xbps-src/libexec/umount.c b/xbps-src/libexec/umount.c new file mode 100644 index 0000000000..1ddb778d22 --- /dev/null +++ b/xbps-src/libexec/umount.c @@ -0,0 +1,58 @@ +/* + * Umounts a previously bind mounted filesystem mountpoint, + * by using the CAP_SYS_ADMIN capability set on the file. + * + * Juan RP - 2010/04/26 - Public Domain. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +void +usage(void) +{ + fprintf(stderr, "Usage: xbps-src-capbumount \n"); + exit(EXIT_FAILURE); +} + +int +main(int argc, char **argv) +{ + cap_t cap; + cap_flag_value_t effective, permitted; + int rv; + + if (argc != 2) + usage(); + + cap = cap_get_proc(); + if (cap == NULL) { + fprintf(stderr, "cap_get_proc() returned %s!\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + + cap_get_flag(cap, CAP_SYS_ADMIN, CAP_EFFECTIVE, &effective); + cap_get_flag(cap, CAP_SYS_ADMIN, CAP_PERMITTED, &permitted); + if ((effective != CAP_SET) && (permitted != CAP_SET)) { + fprintf(stderr, "E: missing 'cap_sys_admin' capability!\n" + "Please set it with: setcap cap_sys_admin=ep %s'\n", + argv[0]); + cap_free(cap); + exit(EXIT_FAILURE); + } + cap_free(cap); + + if ((rv = umount(argv[1])) != 0) { + fprintf(stderr, "E: cannot umount %s: %s\n", argv[0], + strerror(errno)); + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} diff --git a/xbps-src/libexec/xbps-src-chroot-helper.sh.in b/xbps-src/libexec/xbps-src-chroot-helper.sh.in index ed301dae44..b4c287fb9d 100644 --- a/xbps-src/libexec/xbps-src-chroot-helper.sh.in +++ b/xbps-src/libexec/xbps-src-chroot-helper.sh.in @@ -35,20 +35,26 @@ REQFS="sys proc dev xbps" mount_chroot_fs() { - local cnt f blah + local cnt f blah dowrite for f in ${REQFS}; do if [ ! -f ${XBPS_MASTERDIR}/.${f}_mount_bind_done ]; then + unset dowrite echo -n "=> Mounting /${f} in chroot... " if [ ! -d ${XBPS_MASTERDIR}/${f} ]; then mkdir -p ${XBPS_MASTERDIR}/${f} fi case ${f} in - xbps) blah=${XBPS_DISTRIBUTIONDIR};; + xbps) + blah=${XBPS_DISTRIBUTIONDIR} + dowrite="-w" + ;; *) blah=/${f};; esac [ ! -d ${blah} ] && echo "failed." && continue - mount --bind ${blah} ${XBPS_MASTERDIR}/${f} + @@XBPS_INSTALL_LIBEXECDIR@@/xbps-src-chroot-capmount \ + ${dowrite} ${blah} ${XBPS_MASTERDIR}/${f} \ + 2>/dev/null if [ $? -eq 0 ]; then echo 1 > ${XBPS_MASTERDIR}/.${f}_mount_bind_done echo "done." @@ -75,7 +81,8 @@ umount_chroot_fs() echo ${cnt} > ${XBPS_MASTERDIR}/.${fs}_mount_bind_done else echo -n "=> Unmounting ${fs} from chroot... " - umount -f ${XBPS_MASTERDIR}/${fs} + @@XBPS_INSTALL_LIBEXECDIR@@/xbps-src-chroot-capumount \ + ${XBPS_MASTERDIR}/${fs} 2>/dev/null if [ $? -eq 0 ]; then rm -f ${XBPS_MASTERDIR}/.${fs}_mount_bind_done echo "done." diff --git a/xbps-src/shutils/chroot.sh.in b/xbps-src/shutils/chroot.sh.in index 0d36023fcc..62b870a162 100644 --- a/xbps-src/shutils/chroot.sh.in +++ b/xbps-src/shutils/chroot.sh.in @@ -38,8 +38,6 @@ if [ "${chroot_cmd}" = "chroot" ]; then echo "Root permissions are required for the chroot, try again." exit 1 fi -else - chroot_cmd_args="--" fi . $XBPS_SHUTILSDIR/builddep_funcs.sh @@ -154,7 +152,7 @@ prepare_binpkg_repos() { if [ ! -f "$XBPS_MASTERDIR/.xbps_added_local_repo" ]; then msg_normal "Registering local binpkg repo..." - ${chroot_cmd} $XBPS_MASTERDIR ${chroot_cmd_args} \ + ${chroot_cmd} $XBPS_MASTERDIR \ xbps-repo.static add /xbps_packagesdir [ $? -eq 0 ] && touch -f $XBPS_MASTERDIR/.xbps_added_local_repo fi @@ -233,12 +231,12 @@ xbps_chroot_handler() # Reinstall xbps-src in the chroot if [ ! -f $XBPS_MASTERDIR/usr/local/sbin/xbps-src ]; then env in_chroot=yes LANG=C PATH=$path \ - ${chroot_cmd} $XBPS_MASTERDIR ${chroot_cmd_args} sh -c \ - "cd /xbps/xbps-src && make install clean" + ${chroot_cmd} $XBPS_MASTERDIR sh -c \ + "cd /xbps/xbps-src && make IN_CHROOT=1 install clean" fi if [ "$action" = "chroot" ]; then - env in_chroot=yes LANG=C PATH=$path \ + env in_chroot=yes IN_CHROOT=1 LANG=C PATH=$path \ ${chroot_cmd} $XBPS_MASTERDIR /bin/sh else local lenv @@ -247,8 +245,7 @@ xbps_chroot_handler() [ -n "$norm_builddir" ] && \ action="-C $action" env in_chroot=yes LANG=C PATH=$path \ - ${lenv} ${chroot_cmd} $XBPS_MASTERDIR \ - ${chroot_cmd_args} sh -c \ + ${lenv} ${chroot_cmd} $XBPS_MASTERDIR sh -c \ "cd /xbps/srcpkgs/$pkg && xbps-src $action" fi msg_normal "Exiting from the chroot on $XBPS_MASTERDIR." diff --git a/xbps-src/xbps-src.sh.in b/xbps-src/xbps-src.sh.in index cb76542540..2484d0b650 100644 --- a/xbps-src/xbps-src.sh.in +++ b/xbps-src/xbps-src.sh.in @@ -163,8 +163,14 @@ set_defvars . $XBPS_SHUTILSDIR/common_funcs.sh +if [ -n "$XBPS_USE_CAPCHROOT" ]; then + chroot_cmd="@@XBPS_INSTALL_LIBEXECDIR@@/xbps-src-capchroot" + unset sudo_cmd +fi + if [ "$(id -u)" -eq 0 ]; then # disable sudo and fakeroot if uid==0 + chroot_cmd="chroot" unset sudo_cmd if [ -n "$in_chroot" ]; then unset fakeroot_cmd @@ -172,10 +178,6 @@ if [ "$(id -u)" -eq 0 ]; then fi fi -if [ -n "$XBPS_USE_CAPCHROOT" ]; then - chroot_cmd="capchroot" -fi - # Main switch case "$target" in build|configure)