Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

uid/gidmapping #145

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh

wget https://github.com/ezrizhu/gidmapper/releases/download/0.0.3/gidmapper -O /usr/bin/gidmapper
ezrizhu marked this conversation as resolved.
Show resolved Hide resolved
chmod +x /usr/bin/gidmapper
setcap 'CAP_SETGID=ep' /usr/bin/gidmapper
39 changes: 39 additions & 0 deletions test/fileowner.sh
Original file line number Diff line number Diff line change
@@ -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"
14 changes: 14 additions & 0 deletions test/gidmapping.sh
Original file line number Diff line number Diff line change
@@ -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
8 changes: 3 additions & 5 deletions test/toplevel-perms.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand All @@ -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"
14 changes: 14 additions & 0 deletions test/uidmapping.sh
Original file line number Diff line number Diff line change
@@ -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
65 changes: 59 additions & 6 deletions try
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ autodetect_union_helper() {
fi
}

# notify the mapper that we're up
echo "a" > "$SOCKET"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use different sentinels when sending to and receiving from the mapper---we should check that the sentinels are what we expect, too.


# 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
Expand Down Expand Up @@ -257,30 +263,46 @@ EOF
cat >"$chroot_executable" <<EOF
#!/bin/sh

unset START_DIR SANDBOX_DIR UNION_HELPER DIRS_AND_MOUNTS TRY_EXIT_STATUS
unset START_DIR SANDBOX_DIR UNION_HELPER DIRS_AND_MOUNTS TRY_EXIT_STATUS SOCKET
unset script_to_execute chroot_executable try_mount_log

mount -t proc proc /proc &&
cd "$START_DIR" &&
. "$script_to_execute"
if [ "$EUSER" ]
then
su - $EUSER -c "cd "$START_DIR" && sh $script_to_execute"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we confident that all of the other variables we care about are marked as export?

This should probably be exec su ..., since we have no other work afterwards. In fact, we could probably do cd "$START_DIR" && exec su - ....

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a test to ensure that:

(a) normal (non-sudo) use of try doesn't let you read files you should not have
(b) sudo within the normal try container doesn't leak privileges
(c) we need to carefully articulate how we lose privileges after having run --map-root-user

else
cd "$START_DIR" &&
. "$script_to_execute"
fi
EOF

echo "$@" >"$script_to_execute"

# `$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
Expand Down Expand Up @@ -533,6 +555,30 @@ error() {
exit "$exit_status"
}

################################################################################
# Change uid/gid mapping
################################################################################

mapper() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a more informative name, like uid_gid_mapper()?

cat "$SOCKET" > /dev/null
# Get the pid of the unshare process with current pid as parent
pid=$(pgrep -P $$ -f unshare)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a new external dependency?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we're adding libcap2-bin and procps/procps-ng. readme will be updated appropriately.
libcap2-bin for setup to do addcap to the gidmapper.
procps for pgrep to look for unshare thread id, was hoping for a non-external-dependency needed option, perhaps we can have this be built into the c gidmapper impl.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's write the process id on the socket, save ourselves the trouble.


# 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

investigate gidmapper "$pid" 0 "$(id -u)" 1 0 "$(id -g)" 1? why map all groups?

fi

# Notify the unshare process that we have finished
echo "a" > "$SOCKET"
}

################################################################################
# Argument parsing
################################################################################
Expand All @@ -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)
Expand Down Expand Up @@ -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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to unconditionally clear EUSER at startup, so that it can't be invoked as EUSER=foo try .... I suggest using a more specific name, like TRYUSER.

export EUSER;;
(D) if ! [ -d "$OPTARG" ]
then
error "could not find sandbox directory '$OPTARG'" 2
Expand Down
56 changes: 56 additions & 0 deletions util/mapper/mapper.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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;
}
Loading