Last active
December 23, 2019 08:30
-
-
Save thediveo/62b9d172c96ea08007c0ffc000d9de08 to your computer and use it in GitHub Desktop.
Can child(!) PID namespaces be owned by parent(!) user namespaces?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Test the hypothesis that PID namespace hierarchy cannot "run on reverse" to | |
# the user namespace hierarchy. In particular, a PID child namespace cannot be | |
# owned by a parent user namespace. However, a PID namespace can be a child of a | |
# PID namespace owned by a parent user namespace. | |
echo "trying to create a child PID namespace which is owned by a parent user namespace..." | |
NSTEXTREPR='([[:alpha:]]+):\[([[:digit:]]+)\]' | |
SECTION="--------------------" | |
# Given a Linux kernel namespace in textual form "type:[id]", returns (prints) | |
# a PID suitable for further reference. | |
function nsreadlink_to_pid { | |
if [[ $1 =~ $NSTEXTREPR ]]; then | |
# Extract the type of namespace and its unique id separately... | |
NSTYPE=${BASH_REMATCH[1]} | |
NSID=${BASH_REMATCH[2]} | |
# ...then try to find a namespace id match in what lsns discovers for | |
# the specific type of namespace, so we get a suitable PID | |
# corresponding with the namespace in question. | |
PID=$(lsns -o NS,PID -n -t "${NSTYPE}" | awk "\$1 == ${NSID} { print \$2 }") | |
echo ${PID} | |
else | |
echo "invalid namespace textual representation: \"$1\"" | |
exit 1 | |
fi | |
} | |
# Creates new user namespace #1, then inside it, it creates another new user | |
# namespace #2 as well as a new PID namespace #2, owned by user namespace #2. | |
# Just for completeness: user namespace #0 normally will be the root user | |
# namespace or whichever user namespace is active at the time this script is | |
# run. | |
# user#0 -- already existing when script runs | |
# └── user#1 | |
# └── user#2 ⟜─ pid#2 | |
echo ${SECTION} | |
echo "creating user#1-->user#2+pid#2..." | |
exec 2> /dev/null 3< <(unshare -U -r /bin/bash -c "sleep infinity 42s & readlink /proc/self/ns/user && unshare -U -p -f -r /bin/bash -c \"readlink /proc/self/ns/user /proc/self/ns/pid && sleep infinity 42s\"") | |
read <&3 USERNS1; USERNS1PID=$(nsreadlink_to_pid ${USERNS1}) | |
echo "user#1 ${USERNS1} -> PID ${USERNS1PID}" | |
read <&3 USERNS2; USERNS2PID=$(nsreadlink_to_pid ${USERNS2}) | |
echo "user#2 ${USERNS2} -> PID ${USERNS2PID}" | |
read <&3 PIDNS2; PIDNS2PID=$(nsreadlink_to_pid ${PIDNS2}) | |
echo "pid#2 ${PIDNS2} -> PID ${PIDNS2PID}" | |
# For simple starters, let's enter user namespace #1, and create a new PID | |
# namespace there. This new PID namespace #X is owned by user namespace #1. This | |
# should succeed without any issues, and is just to show that later joining user | |
# namespace #1 allows us to create new pid namespaces. Additionally, this shows | |
# that we can have a child PID namespace #X which is owned by user namespace #1, | |
# yet is a child of PID namespace #0 -- well, otherwise containers wouldn't work | |
# ;) | |
# user#0 ⟜───────── pid#0 | |
# ↑ ↑ ↑ | |
# └── user#1 ⟜─ pid#X │ | |
# ↑ │ | |
# └── user#2 ⟜─ pid#2 | |
echo ${SECTION} | |
echo "entering user#1, creating pid#X" | |
# Note, we keep PID namespace #X open in case someone wants to dump the | |
# namespace configuration in this script... | |
sudo nsenter --user=/proc/${USERNS1PID}/ns/user \ | |
/bin/bash -c "echo \"entered user#1...\" && unshare -r -p echo -n \"pid#X: \" && readlink /proc/self/ns/pid && echo \"succeeded creating pid#X in #user1\" && sleep infinity 42s || echo '!!! creating pid#X in user#1 failed !!!'" & | |
# Now enter user namespace #1 and PID namespace #2. Please note that PID | |
# namespace #2 is owned by user namespace #2, not: user namespace #1. Trying | |
# to then create a new PID namespace (#1) would mean that PID namespace #1 | |
# should be a child of PID namespace #2, yet the the owner of PID namespace #1 | |
# would be user namespace #1, which would cause kind of a hierarchy confusion. | |
# user#0 | |
# └── user#1 ⟜────── pid#1 | |
# ↑ ↓ ???not allowed? | |
# └── user#2 ⟜─ pid#2 | |
echo ${SECTION} | |
echo "entering user#1 (sic!) and pid#2, then trying to create pid#1..." | |
sudo nsenter --user=/proc/${USERNS1PID}/ns/user --pid=/proc/${PIDNS2PID}/ns/pid \ | |
/bin/bash -c "echo \"entered user#1+pid#2...\" && unshare -f -r -p /bin/true && echo \"succeeded creating pid#1 in #user1\" || echo '!!! creating pid#1 as child of pid#2 while in user#1 failed !!!'" | |
# Clean up the newly created namespaces by killing the inner sleep, which then | |
# will cause the tower of processes to collapse, which in turn will cause the | |
# namespaces to cease to exists. | |
echo ${SECTION} | |
echo "cleaning up processes and namespaces..." | |
PIDS=$(ps -o pid,command a | grep '[[:digit:]]\+ sleep infinity 42s' | awk '{ print $1 }') | |
while IFS= read -r PID; do | |
echo -n "terminating PID ${PID}... " | |
kill -SIGKILL $PID | |
while kill -0 $PID 2>/dev/null; do | |
sleep 1 | |
done | |
echo "done." | |
done <<< "$PIDS" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment