1

When testing compatibility for an update of a legacy program in OpenSUSE, I traced the problem to a strange version difference. The following C program creates a child process in sudo, and then kills this process in sudo:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main (int argc, char *argv[]) 
{
    char execName[80] = "/usr/bin/sudo";
    char cmd[128];
    int pid = 0;
    int SIG = 15;
    if (argc > 1) SIG = atoi(argv[1]);

    printf("Sudo sleeping for 30 seconds.\n");
    printf("Sudo killing with signal %d.\n", SIG);
    printf("Use `ps aux | grep sleep` within 30 seconds to verify it was killed. Only this grep should show.\n");
    if ((pid = vfork()) == 0) {
        execl (execName, execName, "/bin/sleep", "30", (char *)NULL);
        perror(execName);
        exit (-1); 
    } else {
        if (pid == -1) perror("Bad PID for sleeper");
        fprintf( stderr, "sleeper process PID = %d\n", pid );
    }
    sprintf(cmd, "/usr/bin/sudo kill -%d %d", SIG, pid);
    system(cmd);
}

In order to execute this code, users need to be given access to sudo, and sudoers needs to be given access to kill. This accurately represents how the legacy program works, except the sleeper program is a process that runs continuously, and there are more steps in between. Unfortunately, it is not realistic to abandon this program entirely.

In OpenSUSE 13.2, this program successfully kills the sleeper. However, in OpenSUSE 42.3, the sleeper remains. Why? How can I rewrite this so that I can give my own sudo children in OpenSUSE 42.3 a kill signal?

As an experiment, I changed the final system call to /usr/bin/sudo pkill -15 -P %d, where %d is the process ID. For some reason, this worked across versions, although not reliably. None of the theories I had for why these differ could adequately explain why this works.

Hyreon
  • 11
  • 4
  • If `execl()` fails, you can't safely call `perror()` nor `exit()` after `vfork() - they will modify the address space of the *parent* process and invoke UB. Nevermind that code shouldn't be using `vfork()` in the first place. – Andrew Henle May 26 '23 at 17:11

1 Answers1

1

The sleeper is a child process of sudo. You're only killing the sudo process, not its children.

You can kill an entire process group by negating the PID of the process group leading. This will work as long as sudo doesn't create a new process group for the children, which doesn't seem likely.

    sprintf(cmd, "/usr/bin/sudo kill -%d %d", SIG, -pid);

Also, if fork() returns -1 you should exit after printing the error message. The usual structure looks like this:

if ((pid = vfork()) == 0) {
    // child code
} else if (pid == -1) {
    // report error
} else {
    // parent code
}
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • This is similar to the solution I suggested with pkill, with the slight difference that it never works: `sending signal to -9450 failed: No such process`. – Hyreon May 26 '23 at 19:12
  • I think you may need to make the child process a process group leader, `sudo` doesn't do that itself. – Barmar May 26 '23 at 19:17