/* * SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko * SPDX-FileCopyrightText: 2007 - 2009, Nicolas François * * SPDX-License-Identifier: BSD-3-Clause */ #include #ident "$Id: $" #include #include #include #include #include #include #include "defines.h" #include "prototypes.h" #ifdef ENABLE_SUBIDS #include "subordinateio.h" #endif /* ENABLE_SUBIDS */ #include "shadowlog.h" #ifdef __linux__ static int check_status (const char *name, const char *sname, uid_t uid); static int user_busy_processes (const char *name, uid_t uid); #else /* !__linux__ */ static int user_busy_utmp (const char *name); #endif /* !__linux__ */ /* * user_busy - check if a user is currently running processes */ int user_busy (const char *name, uid_t uid) { /* There are no standard ways to get the list of processes. * An option could be to run an external tool (ps). */ #ifdef __linux__ /* On Linux, directly parse /proc */ return user_busy_processes (name, uid); #else /* !__linux__ */ /* If we cannot rely on /proc, check is there is a record in utmp * indicating that the user is still logged in */ return user_busy_utmp (name); #endif /* !__linux__ */ } #ifndef __linux__ static int user_busy_utmp (const char *name) { struct utmp *utent; setutent (); while ((utent = getutent ()) != NULL) { if (utent->ut_type != USER_PROCESS) { continue; } if (strncmp (utent->ut_user, name, sizeof utent->ut_user) != 0) { continue; } if (kill (utent->ut_pid, 0) != 0) { continue; } fprintf (log_get_logfd(), _("%s: user %s is currently logged in\n"), log_get_progname(), name); return 1; } return 0; } #endif /* !__linux__ */ #ifdef __linux__ #ifdef ENABLE_SUBIDS #define in_parentuid_range(uid) ((uid) >= parentuid && (uid) < parentuid + range) static int different_namespace (const char *sname) { /* 41: /proc/xxxxxxxxxx/task/xxxxxxxxxx/ns/user + \0 */ char path[41]; char buf[512], buf2[512]; ssize_t llen1, llen2; snprintf (path, 41, "/proc/%s/ns/user", sname); if ((llen1 = readlink (path, buf, sizeof(buf))) == -1) return 0; if ((llen2 = readlink ("/proc/self/ns/user", buf2, sizeof(buf2))) == -1) return 0; if (llen1 == llen2 && memcmp (buf, buf2, llen1) == 0) return 0; /* same namespace */ return 1; } #endif /* ENABLE_SUBIDS */ static int check_status (const char *name, const char *sname, uid_t uid) { /* 40: /proc/xxxxxxxxxx/task/xxxxxxxxxx/status + \0 */ char status[40]; char line[1024]; FILE *sfile; snprintf (status, 40, "/proc/%s/status", sname); sfile = fopen (status, "r"); if (NULL == sfile) { return 0; } while (fgets (line, sizeof (line), sfile) == line) { if (strncmp (line, "Uid:\t", 5) == 0) { unsigned long ruid, euid, suid; assert (uid == (unsigned long) uid); (void) fclose (sfile); if (sscanf (line, "Uid:\t%lu\t%lu\t%lu\n", &ruid, &euid, &suid) == 3) { if ( (ruid == (unsigned long) uid) || (euid == (unsigned long) uid) || (suid == (unsigned long) uid) ) { return 1; } #ifdef ENABLE_SUBIDS if ( different_namespace (sname) && ( have_sub_uids(name, ruid, 1) || have_sub_uids(name, euid, 1) || have_sub_uids(name, suid, 1)) ) { return 1; } #endif /* ENABLE_SUBIDS */ } else { /* Ignore errors. This is just a best effort. */ } return 0; } } (void) fclose (sfile); return 0; } static int user_busy_processes (const char *name, uid_t uid) { DIR *proc; struct dirent *ent; char *tmp_d_name; pid_t pid; DIR *task_dir; /* 22: /proc/xxxxxxxxxx/task + \0 */ char task_path[22]; char root_path[22]; struct stat sbroot; struct stat sbroot_process; #ifdef ENABLE_SUBIDS sub_uid_open (O_RDONLY); #endif /* ENABLE_SUBIDS */ proc = opendir ("/proc"); if (proc == NULL) { perror ("opendir /proc"); #ifdef ENABLE_SUBIDS sub_uid_close(); #endif return 0; } if (stat ("/", &sbroot) != 0) { perror ("stat (\"/\")"); (void) closedir (proc); #ifdef ENABLE_SUBIDS sub_uid_close(); #endif return 0; } while ((ent = readdir (proc)) != NULL) { tmp_d_name = ent->d_name; /* * Ingo Molnar's patch introducing NPTL for 2.4 hides * threads in the /proc directory by prepending a period. * This patch is applied by default in some RedHat * kernels. */ if ( (strcmp (tmp_d_name, ".") == 0) || (strcmp (tmp_d_name, "..") == 0)) { continue; } if (*tmp_d_name == '.') { tmp_d_name++; } /* Check if this is a valid PID */ if (get_pid (tmp_d_name, &pid) == 0) { continue; } /* Check if the process is in our chroot */ snprintf (root_path, 22, "/proc/%lu/root", (unsigned long) pid); root_path[21] = '\0'; if (stat (root_path, &sbroot_process) != 0) { continue; } if ( (sbroot.st_dev != sbroot_process.st_dev) || (sbroot.st_ino != sbroot_process.st_ino)) { continue; } if (check_status (name, tmp_d_name, uid) != 0) { (void) closedir (proc); #ifdef ENABLE_SUBIDS sub_uid_close(); #endif fprintf (log_get_logfd(), _("%s: user %s is currently used by process %d\n"), log_get_progname(), name, pid); return 1; } snprintf (task_path, 22, "/proc/%lu/task", (unsigned long) pid); task_path[21] = '\0'; task_dir = opendir (task_path); if (task_dir != NULL) { while ((ent = readdir (task_dir)) != NULL) { pid_t tid; if (get_pid (ent->d_name, &tid) == 0) { continue; } if (tid == pid) { continue; } if (check_status (name, task_path+6, uid) != 0) { (void) closedir (proc); (void) closedir (task_dir); #ifdef ENABLE_SUBIDS sub_uid_close(); #endif fprintf (log_get_logfd(), _("%s: user %s is currently used by process %d\n"), log_get_progname(), name, pid); return 1; } } (void) closedir (task_dir); } else { /* Ignore errors. This is just a best effort */ } } (void) closedir (proc); #ifdef ENABLE_SUBIDS sub_uid_close(); #endif /* ENABLE_SUBIDS */ return 0; } #endif /* __linux__ */