Skip to content

Instantly share code, notes, and snippets.

@corvuscrypto
Last active October 17, 2024 15:30
Show Gist options
  • Save corvuscrypto/cec8255687aa962c3562d0e5c548da37 to your computer and use it in GitHub Desktop.
Save corvuscrypto/cec8255687aa962c3562d0e5c548da37 to your computer and use it in GitHub Desktop.
Golang fork + prctl PDEATHSIG control example
package main
import (
"fmt"
"os"
"os/exec"
"os/signal"
"strconv"
"syscall"
)
// Demonstration of "forking" and setting prctl PDEATH control
// Note: This is not practical since you don't really want to do
// fork/exec in golang. That will really mess things up due to the
// way the thread scheduler works.
// PR_SET_PDEATHSIG is the signal we will use with the prctl syscall so
// the thread (links are made to the parent thread, not necessarily the parent
// process) that forked children will exit when given a SIGKILL signal.
// You don't have to set it to SIGKILL, but I did.
// ####################################################
// # HOW TO VERIFY CORRECT BEHAVIOR #
// ####################################################
// In a separate terminal, run 'ps -a' and verify you have 2 'main' processes.
// Then run 'kill <parent pid>' and verify that the child is also killed.
// Next, comment out line 66 of this code and rebuild/rerun and perform the
// same steps. You'll see that the child should survive and needs to be killed manually
// NOTE: Due to runtime behavior, using ^C on the terminal running this process, it will also
// cleanup the processes tied to this parent process. Cheers :)
const (
PRCTL_SYSCALL = 157
PR_SET_PDEATHSIG = 1
)
func fork() {
cmd := exec.Command(os.Args[0])
// Set the parent process id as an ENVVAR so we can handle the race condition possibility
// (which arises due to the delay between "forking" and setting the PDEATH control)
cmd.Env = append(os.Environ(), fmt.Sprintf("PARENT_PID=%d", os.Getpid()))
cmd.Stdout = os.Stdout
cmd.Start()
fmt.Println("Started child process", cmd.Process.Pid)
}
func setKillSignal() {
_, _, errno := syscall.RawSyscall(uintptr(PRCTL_SYSCALL), uintptr(PR_SET_PDEATHSIG), uintptr(syscall.SIGKILL), 0)
if errno != 0 {
os.Exit(127 + int(errno))
}
// here's the check that prevents an orphan due to the possible race
// condition
if strconv.Itoa(os.Getppid()) != os.Getenv("PARENT_PID") {
os.Exit(1)
}
}
func main() {
if os.Getenv("PARENT_PID") != "" {
// Comment the line below
setKillSignal()
} else {
fmt.Println("Parent ID", os.Getpid())
fork()
}
killChan := make(chan os.Signal)
signal.Notify(killChan, os.Kill)
<-killChan
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment