0

I'm writing a C application that will be run under sudo.

I need to get the original logged in users UID.

I'm calling getuid which the documentation states will return the real UID.

I assumed this would be the logged in user's UID but it instead returns 0 which is the root UID.

Is there an api call which will allow me to get the logged in users UID.

I'm aware of SUDO_UID but would prefer to avoid environment variables (or am I just being difficult).

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
Brett Sutton
  • 3,900
  • 2
  • 28
  • 53
  • doubtful it is possible, since the orignal uid is not preserved, except well in the environment variable, cause sudo is nice and wants you to know. – Anunay Sep 18 '20 at 09:01
  • 1
    Does this answer your question? [How do you find the original user through multiple sudo and su commands?](https://stackoverflow.com/questions/4598001/how-do-you-find-the-original-user-through-multiple-sudo-and-su-commands) – Anunay Sep 18 '20 at 09:08
  • The link you passed doesn't look like it provides reliable results which is critical for this application. – Brett Sutton Sep 18 '20 at 09:15
  • well if there were any reliable "other" ways, I think those would have been posted there, why do you think there would be new ways that will suddenly spring up here. Anyway Why are you avoiding environment variables anyway. – Anunay Sep 18 '20 at 10:08
  • There may be another way, I'm no C expert but, my guess is that if you check what parent process ran `sudo` and get `uid` for that, you would be effectively getting the id of the user that ran sudo. Posting an answer with an example `hello_sudoer.c`, minimal example further checks have to be done to make it secure. – J. García Sep 19 '20 at 18:34

2 Answers2

1

Here is my solution without using environment variables relying on stat() and files in /proc/<pid>, so this makes it linux only. Basically it goes trough the process tree until it finds (sudo) or pid=1, if sudo is found it shows the parent process that called it and the uid for that.

hello_sudoer.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
struct process_info {
    pid_t pid;  
    // max file name is 255 + 2 for "()" in /proc/<pid>/stat
    char process_name[257]; 
    char status;
    pid_t ppid; // parent process id
    uid_t uid; // uid of this process
};

struct process_info get_process_info(pid_t pid){
    struct process_info pi;
    struct stat* s = malloc(sizeof(struct stat));
    memset(&pi, 0, sizeof(struct process_info));
    if(pid > 0) {
        char proc_path[64];
        FILE* proc_stat;
        memset(&proc_path, 0, sizeof(proc_path));
        sprintf(proc_path , "/proc/%d/stat", pid);
        proc_stat = fopen(proc_path, "r");
        if (proc_stat != NULL) {
            fscanf(proc_stat, "%d %s %c %d", 
                    &pi.pid, 
                    pi.process_name, 
                    &pi.status, 
                    &pi.ppid);

            // stat struct doesn't have the parent process id
            // as is only checking on the file properies of /proc/<pid> directory
            // so we can't get the info needed only with stat(2)
            // and stat file doesn't have uid so we can't only use that
            // we need both
            stat(proc_path, s); 
            pi.uid = s -> st_uid;
            free(s);
            fclose(proc_stat);
        } 

    } 
    return pi;
}

void print_process_info(struct process_info pi){
    printf("pid=%d file_name=%s status=%c uid=%d ppid=%d\n", 
            pi.pid, pi.process_name, pi.status, pi.uid, pi.ppid);
}

int main()
{
    pid_t pid = getpid();

    while (pid > 0) {
        struct process_info pi = get_process_info(pid);
        print_process_info(pi);

        if( strcmp("(sudo)" , pi.process_name) == 0 ) {
            // found sudo 
            struct process_info sudo_parent_info = get_process_info(pi.ppid);
            printf("user that ran sudo is uid=%d, from process: \n", 
                    sudo_parent_info.uid);
            print_process_info(sudo_parent_info);
            break;
        }
        pid = pi.ppid;
    }
    return 0;
}

example output:

pid=41769 file_name=(hello_sudoer.o) status=R uid=0 ppid=41749
pid=41749 file_name=(sudo) status=S uid=0 ppid=20078
user that ran sudo is uid=1000, from process: 
pid=20078 file_name=(bash) status=S uid=1000 ppid=5985

J. García
  • 1,859
  • 1
  • 12
  • 13
0

On systems running systemd (or more precisely, systemd-logind), you can get the user that launched the current session using sd_pid_get_owner_uid:

#include <systemd/sd-login.h>
…
uid_t user_id;
if (sd_pid_get_owner(0, &user_id) != 0)
  … /* handle error */
printf("UID: %ju\n", (uintmax_t) user_id);

You need to link with -lsystemd.

This ID will indicate the user that logged into the system. It will not work for processes that do not have a login session (e.g., system services), but in this case, arguably no logged-in user exists for that process anyway. If a user has privileges to launch new sessions or services, they can obscure their original login session, too.

Florian Weimer
  • 32,022
  • 3
  • 48
  • 92
  • I think that's a little problematic as the I'm looking for a solution that ideally works on any distro. Of course there is a chance I may need to do some work to support specific distros. – Brett Sutton Sep 22 '20 at 22:52