I'm writing a program that requires admin privileges (an updater). At some point, we need to start another program and let the first one end. So the parent process makes a fork, detaches from its child and ends execution. As root (using "su" for exemple), everything works as expected. As a regular user, nothing special happens (except of course for writing files as root). But, using sudo, as soon as the parent process ends its execution, its child dies as well.
I wrote a minimal program to debug this issue:
#include <iostream>
#include <fstream>
#include <unistd.h>
int main()
{
std::cout << "start: " << getuid() << std::endl;
if (auto pid = fork() == 0)
{
setpgid(0, 0);
std::cout << "fork has detached" << std::endl;
sleep(1);
std::ofstream test("/root/test.txt");
std::cout << "fork: " << getuid() << " " << test.is_open() << std::endl;
}
else
{
setpgid(pid, 0);
std::cout << "parent: " << getuid() << std::endl;
}
return 0;
}
I expect as output:
$ sudo ./test_sudo
start: 0
parent: 0
fork has detached
$ fork: 0 1
Note: the last line should be written after the prompt.
But with sudo I only get:
$ sudo ./test_sudo
start: 0
parent: 0
fork has detached
$
If I add a wait() call in the parent, the child works as expected with sudo. So the child dies with its parent. The debug tools I tried only showed traces for the parent, how can I track the detached child as well?
Why does using sudo makes my program behave this way? I can work around this issue for my project, but I'm interested in the explanation of this strange behaviour.
This is a security feature. sudo allocates a new pseudoterminal before it executes the specified command. This is to avoid injection attacks from terminals that are not controlled by sudo directly, see for example this SuSE security advisory.
In your case this leads to a slight issue: your program is actually still running, but you won't see its output: if you change the final std::cout << ...
in the child process with the following:
std::ofstream log("/tmp/log");
log << "fork: " << getuid() << " " << test.is_open() << std::endl;
you will see in /tmp/log
that the program still executed. You just didn't get to see the output because sudo deallocated the pseudo-terminal once the parent process was done. So your std::cout
write tried to write to a now-closed pty, which lead to the output being discarded. (std::cout.fail()
would be true after the std::cout << ...
line because the write did actually fail.)