Skip to content

Commit cc4f3f5

Browse files
committed
child: refactor command execution to use goroutines with Pdeathsig
Signed-off-by: fahed dorgaa <[email protected]>
1 parent e83d763 commit cc4f3f5

File tree

3 files changed

+124
-7
lines changed

3 files changed

+124
-7
lines changed

.github/workflows/main.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ jobs:
5252
run: docker run --rm --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0 rootlesskit:test-integration ./integration-ipv6.sh
5353
- name: "Integration test: systemd socket activation"
5454
run: docker run --rm --net=none --privileged rootlesskit:test-integration ./integration-systemd-socket.sh
55+
- name: "Integration test: pdeathsig"
56+
run: docker run --rm --privileged rootlesskit:test-integration ./integration-pdeathsig.sh
5557
- name: "Integration test: Network (network driver=slirp4netns)"
5658
run: |
5759
docker run --rm --privileged rootlesskit:test-integration ./integration-net.sh slirp4netns

hack/integration-pdeathsig.sh

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/bin/bash
2+
# Test script to verify Pdeathsig behavior using rootlesskit itself
3+
# This script:
4+
# 1. Uses rootlesskit to spawn a long-running process
5+
# 2. Kills the rootlesskit parent process
6+
# 3. Verifies that the child process is killed as expected
7+
8+
source $(realpath $(dirname $0))/common.inc.sh
9+
10+
INFO "Starting Pdeathsig test using rootlesskit..."
11+
12+
# Create a temporary directory for test artifacts
13+
TEMP_DIR=$(mktemp -d)
14+
INFO "Created temporary directory: $TEMP_DIR"
15+
16+
# Create a marker file that will be touched by the child process if it's still alive
17+
MARKER_FILE="$TEMP_DIR/child_still_alive"
18+
19+
# Create a script that will be executed by rootlesskit
20+
CHILD_SCRIPT="$TEMP_DIR/child_script.sh"
21+
cat > "$CHILD_SCRIPT" << 'EOF'
22+
#!/bin/bash
23+
echo "Child process started with PID: $$"
24+
echo "Parent PID: $PPID"
25+
26+
# Register a trap to handle signals
27+
trap 'echo "Child received signal, exiting"; exit 1' TERM INT
28+
29+
# Run for 30 seconds, checking if parent is still alive
30+
for i in {1..30}; do
31+
echo "Child still running (iteration $i)..."
32+
33+
# Check if parent has changed (died)
34+
CURRENT_PPID=$(ps -o ppid= -p $$)
35+
if [ "$CURRENT_PPID" != "$PPID" ]; then
36+
echo "Parent changed from $PPID to $CURRENT_PPID"
37+
if [ "$CURRENT_PPID" = "1" ]; then
38+
echo "Parent is now init (PID 1), parent has died"
39+
echo "Child should be killed by Pdeathsig, but if you see this message, it wasn't"
40+
touch MARKER_FILE_PLACEHOLDER
41+
exit 1
42+
fi
43+
fi
44+
45+
sleep 1
46+
done
47+
48+
# If we reach here, the child wasn't killed
49+
echo "Child completed normally (this shouldn't happen if Pdeathsig is working)"
50+
touch MARKER_FILE_PLACEHOLDER
51+
EOF
52+
53+
# Replace the placeholder with the actual marker file path
54+
sed -i "s|MARKER_FILE_PLACEHOLDER|$MARKER_FILE|g" "$CHILD_SCRIPT"
55+
chmod +x "$CHILD_SCRIPT"
56+
57+
# Start rootlesskit with the child script
58+
INFO "Starting rootlesskit..."
59+
$ROOTLESSKIT "$CHILD_SCRIPT" &
60+
ROOTLESSKIT_PID=$!
61+
INFO "Rootlesskit started with PID: $ROOTLESSKIT_PID"
62+
63+
# Wait a moment for the child to start
64+
sleep 2
65+
66+
# Find the child process
67+
CHILD_PID=$(pgrep -P $ROOTLESSKIT_PID)
68+
if [ -z "$CHILD_PID" ]; then
69+
ERROR "Failed to find child process"
70+
exit 1
71+
fi
72+
INFO "Found child process with PID: $CHILD_PID"
73+
74+
# Kill the rootlesskit process
75+
INFO "Killing rootlesskit process (PID: $ROOTLESSKIT_PID)..."
76+
kill -9 $ROOTLESSKIT_PID
77+
78+
# Wait a moment for the child to be killed
79+
sleep 2
80+
81+
# Check if the child process is still running
82+
if ps -p $CHILD_PID > /dev/null; then
83+
ERROR "FAIL: Child process (PID: $CHILD_PID) is still running after parent was killed"
84+
kill -9 $CHILD_PID # Clean up
85+
exit 1
86+
else
87+
INFO "PASS: Child process (PID: $CHILD_PID) was killed as expected"
88+
fi
89+
90+
# Check if the marker file exists
91+
if [ -f "$MARKER_FILE" ]; then
92+
ERROR "FAIL: Marker file exists, which means the child process wasn't killed by Pdeathsig"
93+
exit 1
94+
else
95+
INFO "PASS: Marker file doesn't exist, which means the child process was killed by Pdeathsig"
96+
fi
97+
98+
INFO "Test completed successfully!"
99+
rm -rf "$TEMP_DIR"
100+
exit 0

pkg/child/child.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -442,12 +442,6 @@ func Child(opt Opt) error {
442442

443443
// The parent calls child with Pdeathsig, but it is cleared when newuidmap SUID binary is called
444444
// https://github.com/rootless-containers/rootlesskit/issues/65#issuecomment-492343646
445-
runtime.LockOSThread()
446-
err = unix.Prctl(unix.PR_SET_PDEATHSIG, uintptr(unix.SIGKILL), 0, 0, 0)
447-
runtime.UnlockOSThread()
448-
if err != nil {
449-
return err
450-
}
451445
os.Unsetenv(opt.PipeFDEnvKey)
452446
if err := pipeR.Close(); err != nil {
453447
return fmt.Errorf("failed to close fd %d: %w", pipeFD, err)
@@ -483,8 +477,29 @@ func Child(opt Opt) error {
483477
if err != nil {
484478
return err
485479
}
480+
481+
// Create a channel to receive errors from the goroutine
482+
cmdErrCh := make(chan error, 1)
483+
486484
if opt.Reaper {
487-
if err := runAndReap(cmd); err != nil {
485+
// Launch a goroutine to execute the command with Pdeathsig
486+
go func() {
487+
// Lock the goroutine to the OS thread
488+
runtime.LockOSThread()
489+
defer runtime.UnlockOSThread()
490+
491+
// Set the parent death signal
492+
if err := unix.Prctl(unix.PR_SET_PDEATHSIG, uintptr(unix.SIGKILL), 0, 0, 0); err != nil {
493+
cmdErrCh <- err
494+
return
495+
}
496+
497+
// Run the command
498+
cmdErrCh <- runAndReap(cmd)
499+
}()
500+
501+
// Wait for the command to complete
502+
if err := <-cmdErrCh; err != nil {
488503
return fmt.Errorf("command %v exited: %w", opt.TargetCmd, err)
489504
}
490505
} else {

0 commit comments

Comments
 (0)