diff --git a/src/polkit/polkitunixprocess.c b/src/polkit/polkitunixprocess.c index 95786d0..dd45d86 100644 --- a/src/polkit/polkitunixprocess.c +++ b/src/polkit/polkitunixprocess.c @@ -19,6 +19,13 @@ * Author: David Zeuthen */ +#ifdef __linux__ +#include +#include +#include +#include +#include +#endif #include #ifdef HAVE_FREEBSD #include @@ -151,8 +158,11 @@ struct _PolkitUnixProcess gint pid; guint64 start_time; + guint64 cgroupid; gint uid; gint pidfd; + gint ppidfd; + guint ctty; gboolean pidfd_is_safe; GArray *gids; }; @@ -166,10 +176,14 @@ enum { PROP_0, PROP_PID, + PROP_PPID, PROP_START_TIME, + PROP_CGROUPID, PROP_UID, PROP_PIDFD, PROP_PIDFD_IS_SAFE, + PROP_PPIDFD, + PROP_CTTY, PROP_GIDS, }; @@ -178,6 +192,18 @@ static void subject_iface_init (PolkitSubjectIface *subject_iface); static guint64 get_start_time_for_pid (gint pid, GError **error); +static gint +get_ppidfd_for_pidfd (gint pidfd, + GError **error); + +static guint +get_ctty_number_for_pidfd (gint pidfd, + GError **error); + +static guint64 +get_cgroupid_for_pidfd (gint pidfd, + GError **error); + #if defined(HAVE_FREEBSD) || defined(HAVE_NETBSD) || defined(HAVE_OPENBSD) static gboolean get_kinfo_proc (gint pid, #if defined(HAVE_NETBSD) @@ -196,6 +222,7 @@ polkit_unix_process_init (PolkitUnixProcess *unix_process) { unix_process->uid = -1; unix_process->pidfd = -1; + unix_process->ppidfd = -1; } static void @@ -212,6 +239,10 @@ polkit_unix_process_get_property (GObject *object, g_value_set_int (value, polkit_unix_process_get_pid (unix_process)); break; + case PROP_PPID: + g_value_set_int (value, polkit_unix_process_get_ppid (unix_process)); + break; + case PROP_UID: g_value_set_int (value, unix_process->uid); break; @@ -224,6 +255,14 @@ polkit_unix_process_get_property (GObject *object, g_value_set_int (value, unix_process->pidfd); break; + case PROP_PPIDFD: + g_value_set_int (value, unix_process->ppidfd); + break; + + case PROP_CTTY: + g_value_set_uint (value, unix_process->ctty); + break; + case PROP_PIDFD_IS_SAFE: g_value_set_boolean (value, unix_process->pidfd_is_safe); break; @@ -232,6 +271,10 @@ polkit_unix_process_get_property (GObject *object, g_value_set_uint64 (value, unix_process->start_time); break; + case PROP_CGROUPID: + g_value_set_uint64 (value, unix_process->cgroupid); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -275,8 +318,8 @@ polkit_unix_process_set_property (GObject *object, } static gint -polkit_unix_process_get_pid_from_pidfd (PolkitUnixProcess *process, - GError **error) +polkit_unix_process_get_pid_from_pidfd (gint pidfd, + GError **error) { gint result; gchar *contents; @@ -284,15 +327,14 @@ polkit_unix_process_get_pid_from_pidfd (PolkitUnixProcess *process, gchar filename[64]; guint n; - g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), -1); + g_return_val_if_fail (pidfd >= 0, -1); g_return_val_if_fail (error == NULL || *error == NULL, -1); - g_return_val_if_fail (process->pidfd >= 0, -1); result = -1; lines = NULL; contents = NULL; - g_snprintf (filename, sizeof filename, "/proc/self/fdinfo/%d", process->pidfd); + g_snprintf (filename, sizeof filename, "/proc/self/fdinfo/%d", pidfd); if (!g_file_get_contents (filename, &contents, NULL, @@ -351,6 +393,31 @@ polkit_unix_process_constructed (GObject *object) process->pid = 0; } } + + if (process->pidfd >= 0) + { + GError *error; + error = NULL; + + process->ppidfd = get_ppidfd_for_pidfd (process->pidfd, &error); + if (error != NULL) + { + g_error_free (error); + error = NULL; + process->ppidfd = -1; + } + + process->cgroupid = get_cgroupid_for_pidfd (process->pidfd, &error); + if (error != NULL) + { + g_error_free (error); + error = NULL; + } + + process->ctty = get_ctty_number_for_pidfd (process->pidfd, &error); + if (error != NULL) + g_error_free (error); + } #endif /* HAVE_PIDFD_OPEN */ if (process->start_time == 0) @@ -383,6 +450,12 @@ polkit_unix_process_finalize (GObject *object) process->pidfd = -1; } + if (process->ppidfd >= 0) + { + close (process->ppidfd); + process->ppidfd = -1; + } + if (process->gids) g_array_unref (process->gids); @@ -459,6 +532,24 @@ polkit_unix_process_class_init (PolkitUnixProcessClass *klass) G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK)); + /** + * PolkitUnixProcess:cgroupid: + * + * The start time of the process. + */ + g_object_class_install_property (gobject_class, + PROP_CGROUPID, + g_param_spec_uint64 ("cgroupid", + "ControlGroup ID", + "The ID of the Control Group of the process", + 0, + G_MAXUINT64, + 0, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + /** * PolkitUnixProcess:pidfd: * @@ -478,6 +569,42 @@ polkit_unix_process_class_init (PolkitUnixProcessClass *klass) G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK)); + /** + * PolkitUnixProcess:ppidfd: + * + * The UNIX process' parent id file descriptor. + */ + g_object_class_install_property (gobject_class, + PROP_PPIDFD, + g_param_spec_int ("ppidfd", + "Process' parent ID FD", + "The UNIX process' parent ID file descriptor", + -1, + G_MAXINT, + -1, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * PolkitUnixProcess:ctty: + * + * The UNIX process controlling TTY. + */ + g_object_class_install_property (gobject_class, + PROP_CTTY, + g_param_spec_uint ("ctty", + "Process controlling TTY", + "The UNIX process controlling TTY", + 0, + G_MAXINT, + 0, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + /** * PolkitUnixProcess:pidfd_is_safe: * @@ -604,7 +731,7 @@ polkit_unix_process_get_pid (PolkitUnixProcess *process) if (process->pidfd >= 0) { GError *error = NULL; - gint pid = polkit_unix_process_get_pid_from_pidfd(process, &error); + gint pid = polkit_unix_process_get_pid_from_pidfd(process->pidfd, &error); if (pid > 0) return pid; @@ -616,6 +743,48 @@ polkit_unix_process_get_pid (PolkitUnixProcess *process) return process->pid; } +/** + * polkit_unix_process_get_ppid: + * @process: A #PolkitUnixProcess. + * + * Gets the process' parent id for @process. + * + * Returns: The process id for the parent of @process. + */ +gint +polkit_unix_process_get_ppid (PolkitUnixProcess *process) +{ + GError *error = NULL; + gint ppid; + + g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), 0); + + if (process->ppidfd < 0) + return 0; + + ppid = polkit_unix_process_get_pid_from_pidfd(process->ppidfd, &error); + if (ppid > 0) + return ppid; + + g_error_free (error); + return 0; +} + +/** + * polkit_unix_process_get_cgroupid: + * @process: A #PolkitUnixProcess. + * + * Gets the cgroupid of @process. + * + * Returns: The cgroupid of @process. + */ +guint64 +polkit_unix_process_get_cgroupid (PolkitUnixProcess *process) +{ + g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), 0); + return process->cgroupid; +} + /** * polkit_unix_process_get_start_time: * @process: A #PolkitUnixProcess. @@ -671,9 +840,23 @@ polkit_unix_process_set_pid (PolkitUnixProcess *process, gint pidfd = (int) syscall (SYS_pidfd_open, process->pid, 0); if (pidfd >= 0) { + GError *error; + error = NULL; + process->pidfd_is_safe = FALSE; process->pidfd = pidfd; process->pid = 0; + if (process->ppidfd >= 0) + { + close (process->ppidfd); + process->ppidfd = -1; + } + process->ppidfd = get_ppidfd_for_pidfd (process->pidfd, &error); + if (error != NULL) + { + g_error_free (error); + process->ppidfd = -1; + } return; } } @@ -697,6 +880,36 @@ polkit_unix_process_get_pidfd (PolkitUnixProcess *process) return process->pidfd; } +/** + * polkit_unix_process_get_ppidfd: + * @process: A #PolkitUnixProcess. + * + * Gets the process' parent id file descriptor for @process. + * + * Returns: The process id file descriptor for the parent of @process. + */ +gint +polkit_unix_process_get_ppidfd (PolkitUnixProcess *process) +{ + g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), -1); + return process->ppidfd; +} + +/** + * polkit_unix_process_get_ctty: + * @process: A #PolkitUnixProcess. + * + * Gets the controlling TTY for @process. + * + * Returns: The dev_t of the controlling TTY of @process. + */ +guint +polkit_unix_process_get_ctty (PolkitUnixProcess *process) +{ + g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), 0); + return process->ctty; +} + /** * polkit_unix_process_get_pidfd_is_safe: * @process: A #PolkitUnixProcess. @@ -731,6 +944,24 @@ polkit_unix_process_set_pidfd (PolkitUnixProcess *process, process->pidfd_is_safe = FALSE; } process->pidfd = pidfd; + + if (process->pidfd >= 0) + { + GError *error; + error = NULL; + + if (process->ppidfd >= 0) + { + close (process->ppidfd); + process->ppidfd = -1; + } + process->ppidfd = get_ppidfd_for_pidfd (process->pidfd, &error); + if (error != NULL) + { + g_error_free (error); + process->ppidfd = -1; + } + } } /** @@ -1131,6 +1362,445 @@ get_start_time_for_pid (pid_t pid, return start_time; } +static guint64 +get_cgroupid_for_pidfd (gint pidfd, + GError **error) +{ +#ifdef __linux__ + guint64 cgroupid; + gint pid; + gchar *filename; + gchar *cgroup_path; + gchar *contents; + size_t length; + gchar *p; + struct file_handle *fhp; + int mount_id; + + pid = 0; + filename = NULL; + contents = NULL; + cgroup_path = NULL; + cgroupid = 0; + fhp = NULL; + + g_return_val_if_fail (pidfd >= 0, 0); + + pid = polkit_unix_process_get_pid_from_pidfd (pidfd, error); + if (pid <= 0) + return 0; + + filename = g_strdup_printf ("/proc/%d/cgroup", pid); + + if (!g_file_get_contents (filename, &contents, &length, error)) + goto out; + + /* The format is '0::/cgroup/path\n */ + p = strchr (contents, ':'); + if (p == NULL) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error parsing file %s", + filename); + goto out; + } + p += 2; /* skip '::' */ + if (p - contents >= (int) length) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error parsing file %s", + filename); + goto out; + } + + contents[length - 1] = '\0'; /* remove the newline */ + + cgroup_path = g_strdup_printf ("/sys/fs/cgroup%s", p); + + /* We know in advance the size of the field, cgroupid is a uint64 */ + fhp = g_malloc (offsetof(struct file_handle, f_handle) + sizeof(guint64)); + *fhp = (struct file_handle) { + .handle_bytes = sizeof(guint64), + .handle_type = 0xfe, /* FILEID_KERNFS from linux/exportfs.h */ + }; + + if (name_to_handle_at (AT_FDCWD, cgroup_path, fhp, &mount_id, 0) < 0) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error getting cgroup ID for process %d: %s", + pid, + g_strerror (errno)); + goto out; + } + + cgroupid = *(guint64 *) fhp->f_handle; + + /* Ensure the process is still running */ + if (pid != polkit_unix_process_get_pid_from_pidfd (pidfd, error)) + { + if (error && *error == NULL) + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Process exited while parsing cgroup id"); + goto out; + } + + out: + g_free (contents); + g_free (filename); + g_free (cgroup_path); + free (fhp); + + return cgroupid; +#else /* !__linux__ */ + return 0; +#endif /* __linux__ */ +} + +static guint +get_ctty_number_for_pidfd (gint pidfd, + GError **error) +{ +#ifdef __linux__ + guint tty_nr; + gint tty_fd; + gint pid; + dev_t major; + dev_t minor; + struct stat st; + struct statfs stfs; + gchar *tty_name; + gchar *filename; + gchar *contents; + size_t length; + gchar **tokens; + guint num_tokens; + gchar *p; + gchar *endp; + + tty_nr = 0; + tty_fd = -1; + pid = 0; + tty_name = NULL; + contents = NULL; + tokens = NULL; + + g_return_val_if_fail (pidfd >= 0, 0); + + pid = polkit_unix_process_get_pid_from_pidfd (pidfd, error); + if (pid <= 0) + return 0; + + filename = g_strdup_printf ("/proc/%d/stat", pid); + if (!filename) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error allocating memory for procfs path"); + goto out; + } + + if (!g_file_get_contents (filename, &contents, &length, error)) + goto out; + + /* parent's pid is the token at index 1 after the '(process name)' entry - since only this + * field can contain the ')' character, search backwards for this to avoid malicious + * processes trying to fool us + */ + p = strrchr (contents, ')'); + if (p == NULL) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error parsing file %s", + filename); + goto out; + } + p += 2; /* skip ') ' */ + if (p - contents >= (int) length) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error parsing file %s", + filename); + goto out; + } + + tokens = g_strsplit (p, " ", 0); + + num_tokens = g_strv_length (tokens); + + if (num_tokens < 5) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error parsing file %s", + filename); + goto out; + } + + /* + * The string can have negative representation, but it's actually an unsigned int, + * cast it to convert it + */ + tty_nr = (guint) strtoll (tokens[4], &endp, 10); + if (endp == tokens[4]) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error parsing file %s", + filename); + goto out; + } + + if (tty_nr == 0) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "No TTY associated with process %d", + pid); + goto out; + } + + /* Now do some sanity checks to ensure we are really looking at a TTY. */ + + /* + * As per the manpage, the minor device number is contained in the + * combination of bits 31 to 20 and 7 to 0, the major device number is in + * bits 15 to 8. + */ + major = (tty_nr & 0xff00) >> 8; + minor = ((tty_nr & 0xfff00000) >> 12) | (tty_nr & 0xff); + + tty_name = g_strdup_printf ("/dev/pts/%lu", minor); + + tty_fd = open (tty_name, O_RDONLY|O_CLOEXEC); + if (tty_fd < 0) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error opening TTY %s for process %d: %s", + tty_name, + pid, + g_strerror (errno)); + goto out; + } + + if (fstat (tty_fd, &st) < 0) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error getting TTY name for process %d: %s", + pid, + g_strerror (errno)); + goto out; + } + + if (!S_ISCHR (st.st_mode)) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "TTY name for process %d is not a character device", + pid); + goto out; + } + + if (major != major (st.st_rdev) || minor != minor (st.st_rdev)) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "TTY device number mismatch for process %d", + pid); + goto out; + } + + if (fstatfs (tty_fd, &stfs) < 0) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error getting TTY filesystem information for process %d: %s", + pid, + g_strerror (errno)); + goto out; + } + + if (stfs.f_type != 0x1cd1) /* 0x1cd1 is the magic number for devpts */ + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "TTY filesystem type mismatch for process %d", + pid); + goto out; + } + + /* + * The ptmx device is weird, it exists twice, once inside and once outside devpts. To detect the + * latter case, let's fire off an ioctl() that only works on ptmx devices. + */ + int v; + if (ioctl (tty_fd, TIOCGPKT, &v) < 0) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error getting TTY packet mode for process %d: %s", + pid, + g_strerror (errno)); + goto out; + } + + /* Ensure the process is still running */ + if (pid != polkit_unix_process_get_pid_from_pidfd (pidfd, error)) + { + if (error && *error == NULL) + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Process exited while reading TTY name"); + goto out; + } + + out: + g_strfreev (tokens); + g_free (filename); + g_free (contents); + g_free (tty_name); + if (tty_fd >= 0) + close (tty_fd); + + return tty_nr; +#else /* !__linux__ */ + return 0; +#endif /* __linux__ */ +} + +static gint +get_ppidfd_for_pidfd (gint pidfd, + GError **error) +{ + gint ppidfd; + gint pid, ppid; + gchar *filename; + gchar *contents; + size_t length; + gchar **tokens; + guint num_tokens; + gchar *p; + gchar *endp; + + ppidfd = -1; + pid = ppid = 0; + contents = NULL; + + g_return_val_if_fail (pidfd >= 0, -1); + + pid = polkit_unix_process_get_pid_from_pidfd (pidfd, error); + if (pid <= 0) + return -1; + + filename = g_strdup_printf ("/proc/%d/stat", pid); + + if (!g_file_get_contents (filename, &contents, &length, error)) + goto out; + + /* parent's pid is the token at index 1 after the '(process name)' entry - since only this + * field can contain the ')' character, search backwards for this to avoid malicious + * processes trying to fool us + */ + p = strrchr (contents, ')'); + if (p == NULL) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error parsing file %s", + filename); + goto out; + } + p += 2; /* skip ') ' */ + if (p - contents >= (int) length) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error parsing file %s", + filename); + goto out; + } + + tokens = g_strsplit (p, " ", 0); + + num_tokens = g_strv_length (tokens); + + if (num_tokens < 2) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error parsing file %s", + filename); + goto out; + } + + ppid = strtoull (tokens[1], &endp, 10); + if (endp == tokens[1]) + { + g_set_error (error, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Error parsing file %s", + filename); + goto out; + } + + g_strfreev (tokens); + +#ifdef HAVE_PIDFD_OPEN + /* + * Ensure the actual parent hasn't exited, in which case the process would + * be reparented to PID 1 + */ + if (ppid > 1) + { + ppidfd = (int) syscall (SYS_pidfd_open, ppid, 0); + + /* Ensure the processes are still running */ + if (ppidfd >= 0 && + (pid != polkit_unix_process_get_pid_from_pidfd (pidfd, error) || + ppid != polkit_unix_process_get_pid_from_pidfd (ppidfd, error))) + { + close (ppidfd); + ppidfd = -1; + } + } +#endif /* HAVE_PIDFD_OPEN */ + + out: + g_free (filename); + g_free (contents); + + return ppidfd; +} + /* * Private: Return the "current" UID. Note that this is inherently racy, * and the value may already be obsolete by the time this function returns; diff --git a/src/polkit/polkitunixprocess.h b/src/polkit/polkitunixprocess.h index 7c9adde..e7133b8 100644 --- a/src/polkit/polkitunixprocess.h +++ b/src/polkit/polkitunixprocess.h @@ -60,6 +60,7 @@ PolkitSubject *polkit_unix_process_new_pidfd (gint pidfd, GArray *gids); GArray *polkit_unix_process_get_gids (PolkitUnixProcess *process); gint polkit_unix_process_get_pid (PolkitUnixProcess *process); +gint polkit_unix_process_get_ppid (PolkitUnixProcess *process); guint64 polkit_unix_process_get_start_time (PolkitUnixProcess *process); gint polkit_unix_process_get_uid (PolkitUnixProcess *process); void polkit_unix_process_set_gids (PolkitUnixProcess *process, @@ -73,6 +74,9 @@ void polkit_unix_process_set_start_time (PolkitUnixProcess *process, gint polkit_unix_process_get_owner (PolkitUnixProcess *process, GError **error) G_GNUC_DEPRECATED_FOR (polkit_unix_process_get_uid); +gint polkit_unix_process_get_ppidfd (PolkitUnixProcess *process); +guint polkit_unix_process_get_ctty (PolkitUnixProcess *process); +guint64 polkit_unix_process_get_cgroupid (PolkitUnixProcess *process); gint polkit_unix_process_get_pidfd (PolkitUnixProcess *process); void polkit_unix_process_set_pidfd (PolkitUnixProcess *process, gint pidfd); diff --git a/src/polkitbackend/polkitbackendinteractiveauthority.c b/src/polkitbackend/polkitbackendinteractiveauthority.c index 84ceec5..cc44881 100644 --- a/src/polkitbackend/polkitbackendinteractiveauthority.c +++ b/src/polkitbackend/polkitbackendinteractiveauthority.c @@ -30,6 +30,10 @@ #include #include #include +#ifdef __linux__ +#include +#include +#endif #include #include "polkitbackendinteractiveauthority.h" @@ -3116,13 +3120,162 @@ convert_temporary_authorization_subject (PolkitSubject *subject) } } +#ifdef __linux__ +static time_t +parse_boottime (void) +{ + static time_t btime = 0; + gchar *contents; + gchar **lines; + guint n; + + if (btime != 0) + return btime; + + lines = NULL; + contents = NULL; + + if (!g_file_get_contents ("/proc/stat", &contents, NULL, NULL)) + return -1; + + lines = g_strsplit (contents, "\n", -1); + for (n = 0; lines != NULL && lines[n] != NULL; n++) + { + if (!g_str_has_prefix (lines[n], "btime ")) + continue; + if (sscanf (lines[n] + 6, "%ld", &btime) != 1) + { + g_strfreev (lines); + g_free (contents); + return -1; + } + break; + } + + g_strfreev (lines); + g_free (contents); + + return btime; +} +#endif /* __linux__ */ + /* See the comment at the top of polkitunixprocess.c */ static gboolean subject_equal_for_authz (PolkitSubject *a, PolkitSubject *b) { if (!polkit_subject_equal (a, b)) - return FALSE; + { +#ifdef __linux__ + PolkitSubject *parent = NULL; + gchar *ctty_path = NULL; + + /* + * If we have two processes, and we can safely track them via PID FDs, + * then we also consider them equal for auth purposes if they share the + * same UID, PPID, Control Group and controlling terminal. + */ + if (!POLKIT_IS_UNIX_PROCESS (a) || !POLKIT_IS_UNIX_PROCESS (b)) + goto error; + + if (!polkit_unix_process_get_pidfd_is_safe (POLKIT_UNIX_PROCESS (a)) || + !polkit_unix_process_get_pidfd_is_safe (POLKIT_UNIX_PROCESS (b))) + goto error; + + int uid_a = polkit_unix_process_get_uid (POLKIT_UNIX_PROCESS (a)); + int uid_b = polkit_unix_process_get_uid (POLKIT_UNIX_PROCESS (b)); + if (uid_a == -1 || uid_b == -1 || uid_a != uid_b) + goto error; + + if (polkit_unix_process_get_pidfd (POLKIT_UNIX_PROCESS (a)) < 0 || + polkit_unix_process_get_pidfd (POLKIT_UNIX_PROCESS (b)) < 0) + goto error; + + /* Ensure the parent process is still running and still the same, and not PID 1 + * (due to reparenting) */ + gint ppid_a = polkit_unix_process_get_ppid (POLKIT_UNIX_PROCESS (a)); + gint ppid_b = polkit_unix_process_get_ppid (POLKIT_UNIX_PROCESS (b)); + if (ppid_a <= 1 || ppid_a != ppid_b) + goto error; + + gint ppid_fd = polkit_unix_process_get_ppidfd (POLKIT_UNIX_PROCESS (a)); + if (ppid_fd < 0) + goto error; + + parent = polkit_unix_process_new_pidfd (ppid_fd, -1, NULL); + if (!parent) + goto error; + + /* Ensure all processes are in the same cgroup */ + if (polkit_unix_process_get_cgroupid (POLKIT_UNIX_PROCESS (a)) != polkit_unix_process_get_cgroupid (POLKIT_UNIX_PROCESS (b)) || + polkit_unix_process_get_cgroupid (POLKIT_UNIX_PROCESS (a)) != polkit_unix_process_get_cgroupid (POLKIT_UNIX_PROCESS (parent)) || + polkit_unix_process_get_cgroupid (POLKIT_UNIX_PROCESS (a)) == 0) + goto error; + + /* + * Ensure the controlling terminal is the same and older than both processes, + * otherwise it might have simply been reopened with the same number. + */ + + guint ttynr_parent = polkit_unix_process_get_ctty (POLKIT_UNIX_PROCESS (parent)); + if (ttynr_parent != polkit_unix_process_get_ctty (POLKIT_UNIX_PROCESS (a)) || + ttynr_parent != polkit_unix_process_get_ctty (POLKIT_UNIX_PROCESS (b)) || + ttynr_parent == 0) + goto error; + + ctty_path = g_strdup_printf ("/dev/pts/%u", ((ttynr_parent & 0xfff00000) >> 12) | (ttynr_parent & 0xff)); + + struct statx st; + if (statx (AT_FDCWD, ctty_path, 0, STATX_CTIME, &st) != 0) + goto error; + + time_t btime = parse_boottime (); + if (btime < 0) + goto error; + + if (st.stx_ctime.tv_sec < btime) + goto error; + + /* TTY creation time is a unix timestamp, while process start time is monotonic, + * subtract the boot timestamp to compare them */ + st.stx_ctime.tv_sec -= btime; + + /* Process start time is in jiffies, so get the jiffies-per-second to + * compare it with the TTY ctime */ + int jiffies = sysconf(_SC_CLK_TCK); + if (jiffies <= 0) + goto error; + + struct statx_timestamp start_time_a; + struct statx_timestamp start_time_b; + + start_time_a.tv_sec = polkit_unix_process_get_start_time (POLKIT_UNIX_PROCESS (a)) / jiffies; + start_time_a.tv_nsec = (polkit_unix_process_get_start_time (POLKIT_UNIX_PROCESS (a)) % jiffies) * 1000000000 / jiffies; + start_time_b.tv_sec = polkit_unix_process_get_start_time (POLKIT_UNIX_PROCESS (b)) / jiffies; + start_time_b.tv_nsec = (polkit_unix_process_get_start_time (POLKIT_UNIX_PROCESS (b)) % jiffies) * 1000000000 / jiffies; + + if (start_time_a.tv_sec < st.stx_ctime.tv_sec || + (start_time_a.tv_sec == st.stx_ctime.tv_sec && start_time_a.tv_nsec < st.stx_ctime.tv_nsec) || + start_time_b.tv_sec < st.stx_ctime.tv_sec || + (start_time_b.tv_sec == st.stx_ctime.tv_sec && start_time_b.tv_nsec < st.stx_ctime.tv_nsec)) + goto error; + + /* Check that the parent is still running at the very end, to avoid races */ + if (polkit_unix_process_get_pid (POLKIT_UNIX_PROCESS (parent)) <= 0) + goto error; + + g_free (ctty_path); + g_object_unref (parent); + return TRUE; + +error: + g_free (ctty_path); + g_object_unref (parent); + return FALSE; +#else /* __linux__ */ + return FALSE; +#endif /* __linux__ */ + } /* Now special case unix processes, as we want to protect against * pid reuse by including the PID FDs or UIDs as a fallback. @@ -3341,7 +3494,7 @@ temporary_authorization_store_add_authorization (TemporaryAuthorizationStore *st on_expiration_timeout, authorization); - if (POLKIT_IS_UNIX_PROCESS (authorization->subject)) + if (POLKIT_IS_UNIX_PROCESS (authorization->subject) && !polkit_unix_process_get_pidfd_is_safe (POLKIT_UNIX_PROCESS (authorization->subject))) { /* For now, set up a timer to poll every two seconds - this is used to determine * when the process vanishes. We want to do this so we can remove the temporary