diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9459628c..b1d29e70 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -28,9 +28,7 @@ jobs: - name: Run tests run: | - cd .. - cp -r try ~ - cd ~/try + sudo ./setup.sh scripts/run_tests.sh - name: Upload script diff --git a/Vagrantfile b/Vagrantfile index 5972795a..1b3f766a 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -11,9 +11,10 @@ Vagrant.configure("2") do |config| debian.vm.provision "file", source: "./", destination: "/home/vagrant/try" debian.vm.provision "shell", privileged: false, inline: " sudo apt-get update - sudo apt-get install -y git expect curl + sudo apt-get install -y git expect curl libcap2-bin sudo chown -R vagrant:vagrant try cd try + sudo ./setup.sh scripts/run_tests.sh " end @@ -24,9 +25,10 @@ Vagrant.configure("2") do |config| debianrustup.vm.provision "file", source: "./", destination: "/home/vagrant/try" debianrustup.vm.provision "shell", privileged: false, inline: " sudo apt-get update - sudo apt-get install -y curl + sudo apt-get install -y curl libcap2-bin sudo chown -R vagrant:vagrant try cd try + sudo ./setup.sh mkdir rustup ./try -D rustup \"curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\" ls -lah rustup/upperdir/home/vagrant/.cargo/bin @@ -39,7 +41,7 @@ Vagrant.configure("2") do |config| debianlvm.vm.provision "file", source: "./", destination: "/home/vagrant/try" debianlvm.vm.provision "shell", privileged: false, inline: " sudo apt-get update - sudo apt-get install -y git expect lvm2 mergerfs curl + sudo apt-get install -y git expect lvm2 mergerfs curl libcap2-bin # Create an image for the lvm disk sudo fallocate -l 2G /root/lvm_disk.img @@ -65,6 +67,7 @@ Vagrant.configure("2") do |config| sudo chown -R vagrant:vagrant /mnt/lv0/try cd /mnt/lv0/try + sudo ./setup.sh scripts/run_tests.sh " end @@ -77,6 +80,7 @@ Vagrant.configure("2") do |config| sudo yum install -y git expect curl sudo chown -R vagrant:vagrant try cd try + sudo ./setup.sh TRY_TOP=$(pwd) scripts/run_tests.sh " end @@ -89,6 +93,7 @@ Vagrant.configure("2") do |config| sudo yum install -y git expect curl sudo chown -R vagrant:vagrant try cd try + sudo ./setup.sh TRY_TOP=$(pwd) scripts/run_tests.sh " end diff --git a/setup.sh b/setup.sh new file mode 100755 index 00000000..679a3e9c --- /dev/null +++ b/setup.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +wget https://github.com/ezrizhu/gidmapper/releases/download/0.0.3/gidmapper -O /usr/bin/gidmapper +chmod +x /usr/bin/gidmapper +setcap 'CAP_SETGID=ep' /usr/bin/gidmapper diff --git a/test/fileowner.sh b/test/fileowner.sh new file mode 100755 index 00000000..8ecb4eb4 --- /dev/null +++ b/test/fileowner.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree)}" +TRY="$TRY_TOP/try" + +cleanup() { + cd / + + if [ -d "$try_workspace" ] + then + rm -rf "$try_workspace" >/dev/null 2>&1 + fi + + if [ -f "$expected" ] + then + rm "$expected" + fi + + if [ -f "$target" ] + then + rm "$target" + fi +} + +trap 'cleanup' EXIT + +try_workspace="$(mktemp -d)" +cd "$try_workspace" || return 9 +touch test + +# Set up expected output +expected="$(mktemp)" +ls -l >"$expected" + +# Set up target output +target="$(mktemp)" + +sudo "$TRY" ls -l | tee "$target" || return 1 +diff -q "$expected" "$target" diff --git a/test/gidmapping.sh b/test/gidmapping.sh new file mode 100755 index 00000000..b9c5e331 --- /dev/null +++ b/test/gidmapping.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree)}" +TRY="$TRY_TOP/try" + +control=$(id -Gn) +testing=$("$TRY" -D "$(mktemp -d)" id -Gn 2>/dev/null) + +if [ "$control" = "$testing" ] +then + exit 0 +else + exit 1 +fi diff --git a/test/toplevel-perms.sh b/test/toplevel-perms.sh index 9f654a8a..71584aed 100755 --- a/test/toplevel-perms.sh +++ b/test/toplevel-perms.sh @@ -34,9 +34,7 @@ cd "$try_workspace" || return 9 touch test cmd="$(mktemp)" -echo "find / -maxdepth 1 -print0 | xargs -0 ls -ld | awk '{print substr(\$1, 1, 10), \$9, \$10, \$11}' | grep -v 'proc' | grep -v 'swap'" > "$cmd" -# Use this after gidmapper to show user and group ownership -#echo "find / -maxdepth 1 -print0 | xargs -0 ls -ld | awk '{print substr(\$1, 1, 10), \$3, \$4, \$9, \$10, \$11}' | grep -v 'proc' | grep -v 'swap'" > "$cmd" +echo "find / -maxdepth 1 -type d -print0 | xargs -0 ls -ld | awk '{print substr(\$1, 1, 10), \$9, \$10, \$11}' | grep -v '/proc' " > "$cmd" # Set up expected output expected="$(mktemp)" @@ -45,6 +43,6 @@ sh "$cmd" >"$expected" # Set up target output target="$(mktemp)" -"$TRY" "sh $cmd" > "$target" || return 1 -#diff -q "$expected" "$target" +# shellcheck disable=SC2024 # sudo won't be used in > $target redirection +sudo "$TRY" "sh $cmd" > "$target" || return 1 diff "$expected" "$target" diff --git a/test/uidmapping.sh b/test/uidmapping.sh new file mode 100755 index 00000000..93758b01 --- /dev/null +++ b/test/uidmapping.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree)}" +TRY="$TRY_TOP/try" + +control=$(id -un) +testing=$(sudo "$TRY" -D "$(mktemp -d)" -u "$USER" id -un) + +if [ "$control" = "$testing" ] +then + exit 0 +else + exit 1 +fi diff --git a/try b/try index dc948f15..a8803dba 100755 --- a/try +++ b/try @@ -172,6 +172,12 @@ autodetect_union_helper() { fi } +# notify the mapper that we're up +echo "a" > "$SOCKET" + +# wait for mapper to finish +cat "$SOCKET" > /dev/null + # Detect if union_helper is set, if not, we try to autodetect them if [ -z "$UNION_HELPER" ] then @@ -257,12 +263,17 @@ EOF cat >"$chroot_executable" <"$script_to_execute" @@ -270,17 +281,28 @@ EOF # `$script_to_execute` need not be +x to be sourced chmod +x "$mount_and_execute" "$chroot_executable" + if [ "$EUSER" ] + then + chown "$EUSER" "$script_to_execute" + fi + # enable job control so interactive commands will play nicely with try asking for user input later(for committing). #5 [ -t 0 ] && set -m + SOCKET="$(mktemp -u)" + mkfifo "$SOCKET" + export SOCKET + + # Running mapper in a subshell to suppress job control [1] + Done message + (mapper&) + # --mount: mounting and unmounting filesystems will not affect the rest of the system outside the unshare - # --map-root-user: map to the superuser UID and GID in the newly created user namespace. # --user: the process will have a distinct set of UIDs, GIDs and capabilities. # --pid: create a new process namespace (needed fr procfs to work right) # --fork: necessary if we do --pid # "Creation of a persistent PID namespace will fail if the --fork option is not also specified." # shellcheck disable=SC2086 # we want field splitting! - unshare --mount --map-root-user --user --pid --fork $EXTRA_NS "$mount_and_execute" + unshare --mount --user --pid --fork $EXTRA_NS "$mount_and_execute" TRY_EXIT_STATUS=$? # remove symlink @@ -533,6 +555,30 @@ error() { exit "$exit_status" } +################################################################################ +# Change uid/gid mapping +################################################################################ + +mapper() { + cat "$SOCKET" > /dev/null + # Get the pid of the unshare process with current pid as parent + pid=$(pgrep -P $$ -f unshare) + + # Map root user to current user, and all groups + # Usage: gidmapper targetpid outeruid inneruid uidcount outergid innergid uidcount + if [ "$(id -u)" = 0 ] + then + # If we're running as root, we can map all the users + gidmapper "$pid" 0 0 65535 0 0 65535 + else + # If not running as root, we can only mount the caller user + gidmapper "$pid" 0 "$(id -u)" 1 0 0 65535 + fi + + # Notify the unshare process that we have finished + echo "a" > "$SOCKET" +} + ################################################################################ # Argument parsing ################################################################################ @@ -544,6 +590,7 @@ Usage: $TRY_COMMAND [-nvhyx] [-i PATTERN] [-D DIR] [-U PATH] [-L dir1:dir2:...] -n don't commit or prompt for commit (overrides -y) -y assume yes to all prompts (overrides -n) -x prevent network access (by unsharing the network namespace) + -u username user to run the command with (requires root) -i PATTERN ignore paths that match PATTERN on summary and commit -D DIR work in DIR (implies -n) -U PATH path to unionfs helper (e.g., mergerfs, unionfs-fuse) @@ -571,13 +618,19 @@ NO_COMMIT="interactive" # Includes all patterns given using the `-i` flag; will be used with `grep -f` IGNORE_FILE="$(mktemp)" -while getopts ":yvnhxi:D:U:L:" opt +while getopts ":yvnhxu:i:D:U:L:" opt do case "$opt" in (y) NO_COMMIT="commit";; (n) NO_COMMIT="show";; (i) echo "$OPTARG" >>"$IGNORE_FILE";; + (u) if [ "$(id -u)" -ne "0" ] + then + error "need root for -u" 2 + fi + EUSER="$OPTARG" + export EUSER;; (D) if ! [ -d "$OPTARG" ] then error "could not find sandbox directory '$OPTARG'" 2 diff --git a/util/mapper/mapper.c b/util/mapper/mapper.c new file mode 100644 index 00000000..4e94502e --- /dev/null +++ b/util/mapper/mapper.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +void* map_uids(void* args) { + char *targetpid = argv[1]; + char *outeruid = argv[2]; + char *inneruid = argv[3]; + char *uidcount = argv[4]; + char *outergid = argv[5]; + char *innergid = argv[6]; + char *gidcount = argv[7]; + + // Build path strings + char uid_path[100]; + sprintf(uid_path, "/proc/%s/uid_map", targetpid); + + char gid_path[100]; + sprintf(gid_path, "/proc/%s/gid_map", targetpid); + + // Build mapping strings + char uid_map[100]; + sprintf(uid_map, "%s %s %s", outeruid, inneruid, uidcount); + + char gid_map[100]; + sprintf(gid_map, "%s %s %s", outergid, innergid, gidcount); + + // Write mappings + FILE *uid_file = fopen(uid_path, "w"); + fprintf(uid_file, "%s", uid_map); + fclose(uid_file); + + FILE *gid_file = fopen(gid_path, "w"); + fprintf(gid_file, "%s", gid_map); + fclose(gid_file); + + return 0; +} + +int main(int argc, char* argv[]) { + + char *usage = "Usage: gidmapper targetpid outeruid inneruid uidcount outergid innergid gidcount"; + + if(argc < 8) { + fprintf(stderr, "%s\n", usage); + exit(1); + } + + pthread_t mapper_thread; + pthread_create(&mapper_thread, NULL, map_uids, NULL); + + pthread_join(mapper_thread, NULL); + + return 0; +}