From 9a44af8ab67d09a2c08be29428b8fe32da809e99 Mon Sep 17 00:00:00 2001
From: David Zeuthen <davidz@redhat.com>
Date: Mon, 11 Apr 2011 12:41:00 -0400
Subject: [PATCH 2/6] Make PolkitUnixProcess also record the uid of the
 process

This is needed to avoid possible TOCTTOU issues since a process can
change both its real uid and effective uid.

Signed-off-by: David Zeuthen <davidz@redhat.com>
---
 docs/polkit/polkit-1-sections.txt |    7 +-
 src/polkit/polkitsubject.c        |   51 ++++--
 src/polkit/polkitunixprocess.c    |  350 +++++++++++++++++++++++++-----------
 src/polkit/polkitunixprocess.h    |   10 +
 4 files changed, 296 insertions(+), 122 deletions(-)

diff --git a/docs/polkit/polkit-1-sections.txt b/docs/polkit/polkit-1-sections.txt
index ac902b6..7f42cd2 100644
--- a/docs/polkit/polkit-1-sections.txt
+++ b/docs/polkit/polkit-1-sections.txt
@@ -148,10 +148,13 @@ POLKIT_UNIX_SESSION_GET_CLASS
 PolkitUnixProcess
 polkit_unix_process_new
 polkit_unix_process_new_full
+polkit_unix_process_new_for_owner
+polkit_unix_process_set_pid
 polkit_unix_process_get_pid
+polkit_unix_process_set_start_time
 polkit_unix_process_get_start_time
-polkit_unix_process_set_pid
-polkit_unix_process_get_owner
+polkit_unix_process_set_uid
+polkit_unix_process_get_uid
 <SUBSECTION Standard>
 PolkitUnixProcessClass
 POLKIT_UNIX_PROCESS
diff --git a/src/polkit/polkitsubject.c b/src/polkit/polkitsubject.c
index d5039a5..02c707b 100644
--- a/src/polkit/polkitsubject.c
+++ b/src/polkit/polkitsubject.c
@@ -24,6 +24,7 @@
 #endif
 
 #include <string.h>
+#include <stdio.h>
 
 #include "polkitsubject.h"
 #include "polkitunixprocess.h"
@@ -209,8 +210,6 @@ polkit_subject_from_string  (const gchar   *str,
                              GError       **error)
 {
   PolkitSubject *subject;
-  guint64 val;
-  gchar *endptr;
 
   g_return_val_if_fail (str != NULL, NULL);
 
@@ -220,12 +219,20 @@ polkit_subject_from_string  (const gchar   *str,
 
   if (g_str_has_prefix (str, "unix-process:"))
     {
-      val = g_ascii_strtoull (str + sizeof "unix-process:" - 1,
-                              &endptr,
-                              10);
-      if (*endptr == '\0')
+      gint scanned_pid;
+      guint64 scanned_starttime;
+      gint scanned_uid;
+      if (sscanf (str, "unix-process:%d:%" G_GUINT64_FORMAT ":%d", &scanned_pid, &scanned_starttime, &scanned_uid) == 3)
         {
-          subject = polkit_unix_process_new ((gint) val);
+          subject = polkit_unix_process_new_for_owner (scanned_pid, scanned_starttime, scanned_uid);
+        }
+      else if (sscanf (str, "unix-process:%d:%" G_GUINT64_FORMAT, &scanned_pid, &scanned_starttime) == 2)
+        {
+          subject = polkit_unix_process_new_full (scanned_pid, scanned_starttime);
+        }
+      else if (sscanf (str, "unix-process:%d", &scanned_pid) == 1)
+        {
+          subject = polkit_unix_process_new (scanned_pid);
           if (polkit_unix_process_get_start_time (POLKIT_UNIX_PROCESS (subject)) == 0)
             {
               g_object_unref (subject);
@@ -233,8 +240,8 @@ polkit_subject_from_string  (const gchar   *str,
               g_set_error (error,
                            POLKIT_ERROR,
                            POLKIT_ERROR_FAILED,
-                           "No process with pid %" G_GUINT64_FORMAT,
-                           val);
+                           "Unable to determine start time for process with pid %d",
+                           scanned_pid);
             }
         }
     }
@@ -268,6 +275,7 @@ polkit_subject_new_for_real (_PolkitSubject *real)
   EggDBusHashMap *details;
   EggDBusVariant *variant;
   EggDBusVariant *variant2;
+  EggDBusVariant *variant3;
 
   s = NULL;
 
@@ -281,10 +289,24 @@ polkit_subject_new_for_real (_PolkitSubject *real)
   else if (strcmp (kind, "unix-process") == 0)
     {
       variant = egg_dbus_hash_map_lookup (details, "pid");
-      variant2 = egg_dbus_hash_map_lookup (details, "start-time");
-      if (variant != NULL && variant2 != NULL)
-        s = polkit_unix_process_new_full (egg_dbus_variant_get_uint (variant),
-                                          egg_dbus_variant_get_uint64 (variant2));
+      if (variant != NULL && egg_dbus_variant_is_uint (variant))
+        {
+          gint pid;
+          guint64 start_time;
+          gint uid;
+          variant2 = egg_dbus_hash_map_lookup (details, "start-time");
+          pid = egg_dbus_variant_get_uint (variant);
+          if (variant2 != NULL && egg_dbus_variant_is_uint64 (variant2))
+            start_time = egg_dbus_variant_get_uint64 (variant2);
+          else
+            start_time = 0;
+          variant3 = egg_dbus_hash_map_lookup (details, "uid");
+          if (variant3 != NULL && egg_dbus_variant_is_int (variant3))
+            uid = egg_dbus_variant_get_int (variant3);
+          else
+            uid = -1;
+          s = polkit_unix_process_new_for_owner (pid, start_time, uid);
+        }
     }
   else if (strcmp (kind, "unix-session") == 0)
     {
@@ -330,6 +352,9 @@ polkit_subject_get_real (PolkitSubject *subject)
       egg_dbus_hash_map_insert (details,
                                 "start-time",
                                 egg_dbus_variant_new_for_uint64 (polkit_unix_process_get_start_time (POLKIT_UNIX_PROCESS (subject))));
+      egg_dbus_hash_map_insert (details,
+                                "uid",
+                                egg_dbus_variant_new_for_int (polkit_unix_process_get_uid (POLKIT_UNIX_PROCESS (subject))));
     }
   else if (POLKIT_IS_UNIX_SESSION (subject))
     {
diff --git a/src/polkit/polkitunixprocess.c b/src/polkit/polkitunixprocess.c
index 868e3c5..eb455f6 100644
--- a/src/polkit/polkitunixprocess.c
+++ b/src/polkit/polkitunixprocess.c
@@ -64,6 +64,7 @@ struct _PolkitUnixProcess
 
   gint pid;
   guint64 start_time;
+  gint uid;
 };
 
 struct _PolkitUnixProcessClass
@@ -76,6 +77,7 @@ enum
   PROP_0,
   PROP_PID,
   PROP_START_TIME,
+  PROP_UID
 };
 
 static void subject_iface_init (PolkitSubjectIface *subject_iface);
@@ -83,6 +85,9 @@ static void subject_iface_init (PolkitSubjectIface *subject_iface);
 static guint64 get_start_time_for_pid (gint    pid,
                                        GError **error);
 
+static gint _polkit_unix_process_get_owner (PolkitUnixProcess  *process,
+                                            GError            **error);
+
 #ifdef HAVE_FREEBSD
 static gboolean get_kinfo_proc (gint pid, struct kinfo_proc *p);
 #endif
@@ -94,6 +99,7 @@ G_DEFINE_TYPE_WITH_CODE (PolkitUnixProcess, polkit_unix_process, G_TYPE_OBJECT,
 static void
 polkit_unix_process_init (PolkitUnixProcess *unix_process)
 {
+  unix_process->uid = -1;
 }
 
 static void
@@ -110,6 +116,10 @@ polkit_unix_process_get_property (GObject    *object,
       g_value_set_int (value, unix_process->pid);
       break;
 
+    case PROP_UID:
+      g_value_set_int (value, unix_process->uid);
+      break;
+
     case PROP_START_TIME:
       g_value_set_uint64 (value, unix_process->start_time);
       break;
@@ -134,6 +144,14 @@ polkit_unix_process_set_property (GObject      *object,
       polkit_unix_process_set_pid (unix_process, g_value_get_int (value));
       break;
 
+    case PROP_UID:
+      polkit_unix_process_set_uid (unix_process, g_value_get_int (value));
+      break;
+
+    case PROP_START_TIME:
+      polkit_unix_process_set_start_time (unix_process, g_value_get_uint64 (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -141,12 +159,39 @@ polkit_unix_process_set_property (GObject      *object,
 }
 
 static void
+polkit_unix_process_constructed (GObject *object)
+{
+  PolkitUnixProcess *process = POLKIT_UNIX_PROCESS (object);
+
+  /* sets start_time and uid in case they are unset */
+
+  if (process->start_time == 0)
+    process->start_time = get_start_time_for_pid (process->pid, NULL);
+
+  if (process->uid == -1)
+    {
+      GError *error;
+      error = NULL;
+      process->uid = _polkit_unix_process_get_owner (process, &error);
+      if (error != NULL)
+        {
+          process->uid = -1;
+          g_error_free (error);
+        }
+    }
+
+  if (G_OBJECT_CLASS (polkit_unix_process_parent_class)->constructed != NULL)
+    G_OBJECT_CLASS (polkit_unix_process_parent_class)->constructed (object);
+}
+
+static void
 polkit_unix_process_class_init (PolkitUnixProcessClass *klass)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 
   gobject_class->get_property = polkit_unix_process_get_property;
   gobject_class->set_property = polkit_unix_process_set_property;
+  gobject_class->constructed = polkit_unix_process_constructed;
 
   /**
    * PolkitUnixProcess:pid:
@@ -158,7 +203,7 @@ polkit_unix_process_class_init (PolkitUnixProcessClass *klass)
                                    g_param_spec_int ("pid",
                                                      "Process ID",
                                                      "The UNIX process ID",
-                                                     -1,
+                                                     0,
                                                      G_MAXINT,
                                                      0,
                                                      G_PARAM_CONSTRUCT |
@@ -168,6 +213,27 @@ polkit_unix_process_class_init (PolkitUnixProcessClass *klass)
                                                      G_PARAM_STATIC_NICK));
 
   /**
+   * PolkitUnixProcess:uid:
+   *
+   * The UNIX user id of the process or -1 if unknown.
+   *
+   * Note that this is the real user-id, not the effective user-id.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_UID,
+                                   g_param_spec_int ("uid",
+                                                     "User ID",
+                                                     "The UNIX user ID",
+                                                     -1,
+                                                     G_MAXINT,
+                                                     -1,
+                                                     G_PARAM_CONSTRUCT |
+                                                     G_PARAM_READWRITE |
+                                                     G_PARAM_STATIC_NAME |
+                                                     G_PARAM_STATIC_BLURB |
+                                                     G_PARAM_STATIC_NICK));
+
+  /**
    * PolkitUnixProcess:start-time:
    *
    * The start time of the process.
@@ -180,7 +246,8 @@ polkit_unix_process_class_init (PolkitUnixProcessClass *klass)
                                                         0,
                                                         G_MAXUINT64,
                                                         0,
-                                                        G_PARAM_READABLE |
+                                                        G_PARAM_CONSTRUCT |
+                                                        G_PARAM_READWRITE |
                                                         G_PARAM_STATIC_NAME |
                                                         G_PARAM_STATIC_BLURB |
                                                         G_PARAM_STATIC_NICK));
@@ -188,109 +255,50 @@ polkit_unix_process_class_init (PolkitUnixProcessClass *klass)
 }
 
 /**
- * polkit_unix_process_get_pid:
+ * polkit_unix_process_get_uid:
  * @process: A #PolkitUnixProcess.
  *
- * Gets the process id for @process.
+ * Gets the user id for @process. Note that this is the real user-id,
+ * not the effective user-id.
  *
- * Returns: The process id for @process.
+ * Returns: The user id for @process or -1 if unknown.
  */
 gint
-polkit_unix_process_get_pid (PolkitUnixProcess *process)
+polkit_unix_process_get_uid (PolkitUnixProcess *process)
 {
-  return process->pid;
+  g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), -1);
+  return process->uid;
 }
 
 /**
- * polkit_unix_process_get_owner:
+ * polkit_unix_process_set_uid:
  * @process: A #PolkitUnixProcess.
- * @error: Return location for error or %NULL.
+ * @uid: The user id to set for @process or -1 to unset it.
  *
- * Gets the uid of the owner of @process.
+ * Sets the (real, not effective) user id for @process.
+ */
+void
+polkit_unix_process_set_uid (PolkitUnixProcess *process,
+                             gint               uid)
+{
+  g_return_if_fail (POLKIT_IS_UNIX_PROCESS (process));
+  g_return_if_fail (uid >= -1);
+  process->uid = uid;
+}
+
+/**
+ * polkit_unix_process_get_pid:
+ * @process: A #PolkitUnixProcess.
  *
- * Note that this returns the real user-id (not the effective user-id) of @process.
+ * Gets the process id for @process.
  *
- * Returns: The UNIX user id of the owner for @process or 0 if @error is set.
- **/
+ * Returns: The process id for @process.
+ */
 gint
-polkit_unix_process_get_owner (PolkitUnixProcess  *process,
-                               GError            **error)
+polkit_unix_process_get_pid (PolkitUnixProcess *process)
 {
-  gint result;
-  gchar *contents;
-  gchar **lines;
-#ifdef HAVE_FREEBSD
-  struct kinfo_proc p;
-#else
-  gchar filename[64];
-  guint n;
-#endif
-
-  result = 0;
-  lines = NULL;
-  contents = NULL;
-
-#ifdef HAVE_FREEBSD
-  if (get_kinfo_proc (process->pid, &p) == 0)
-    {
-      g_set_error (error,
-                   POLKIT_ERROR,
-                   POLKIT_ERROR_FAILED,
-                   "get_kinfo_proc() failed for pid %d: %s",
-                   process->pid,
-                   g_strerror (errno));
-      goto out;
-    }
-
-  result = p.ki_uid;
-#else
-  /* see 'man proc' for layout of the status file
-   *
-   * Uid, Gid: Real, effective, saved set,  and  file  system  UIDs (GIDs).
-   */
-  g_snprintf (filename, sizeof filename, "/proc/%d/status", process->pid);
-  if (!g_file_get_contents (filename,
-                            &contents,
-                            NULL,
-                            error))
-    {
-      goto out;
-    }
-  lines = g_strsplit (contents, "\n", -1);
-  for (n = 0; lines != NULL && lines[n] != NULL; n++)
-    {
-      gint real_uid, effective_uid;
-      if (!g_str_has_prefix (lines[n], "Uid:"))
-        continue;
-      if (sscanf (lines[n] + 4, "%d %d", &real_uid, &effective_uid) != 2)
-        {
-          g_set_error (error,
-                       POLKIT_ERROR,
-                       POLKIT_ERROR_FAILED,
-                       "Unexpected line `%s' in file %s",
-                       lines[n],
-                       filename);
-          goto out;
-        }
-      else
-        {
-          result = real_uid;
-          goto out;
-        }
-    }
-
-  g_set_error (error,
-               POLKIT_ERROR,
-               POLKIT_ERROR_FAILED,
-               "Didn't find any line starting with `Uid:' in file %s",
-               filename);
-#endif
-
- out:
-  g_strfreev (lines);
-  g_free (contents);
-
-  return result;
+  g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), 0);
+  return process->pid;
 }
 
 /**
@@ -304,10 +312,26 @@ polkit_unix_process_get_owner (PolkitUnixProcess  *process,
 guint64
 polkit_unix_process_get_start_time (PolkitUnixProcess *process)
 {
+  g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), 0);
   return process->start_time;
 }
 
 /**
+ * polkit_unix_process_set_start_time:
+ * @process: A #PolkitUnixProcess.
+ * @start_time: The start time for @pid.
+ *
+ * Set the start time of @process.
+ */
+void
+polkit_unix_process_set_start_time (PolkitUnixProcess *process,
+                                    guint64            start_time)
+{
+  g_return_if_fail (POLKIT_IS_UNIX_PROCESS (process));
+  process->start_time = start_time;
+}
+
+/**
  * polkit_unix_process_set_pid:
  * @process: A #PolkitUnixProcess.
  * @pid: A process id.
@@ -318,21 +342,21 @@ void
 polkit_unix_process_set_pid (PolkitUnixProcess *process,
                              gint              pid)
 {
+  g_return_if_fail (POLKIT_IS_UNIX_PROCESS (process));
   process->pid = pid;
-  if (pid != (gint) -1)
-    process->start_time = get_start_time_for_pid (pid, NULL);
 }
 
 /**
  * polkit_unix_process_new:
  * @pid: The process id.
  *
- * Creates a new #PolkitUnixProcess for @pid. The start time of the
- * process will be looked up in using e.g. the
- * <filename>/proc</filename> filesystem depending on the platform in
- * use.
+ * Creates a new #PolkitUnixProcess for @pid.
  *
- * Returns: A #PolkitSubject. Free with g_object_unref().
+ * The uid and start time of the process will be looked up in using
+ * e.g. the <filename>/proc</filename> filesystem depending on the
+ * platform in use.
+ *
+ * Returns: (transfer full): A #PolkitSubject. Free with g_object_unref().
  */
 PolkitSubject *
 polkit_unix_process_new (gint pid)
@@ -349,22 +373,42 @@ polkit_unix_process_new (gint pid)
  *
  * Creates a new #PolkitUnixProcess object for @pid and @start_time.
  *
- * Returns: A #PolkitSubject. Free with g_object_unref().
+ * The uid of the process will be looked up in using e.g. the
+ * <filename>/proc</filename> filesystem depending on the platform in
+ * use.
+ *
+ * Returns: (transfer full): A #PolkitSubject. Free with g_object_unref().
  */
 PolkitSubject *
 polkit_unix_process_new_full (gint pid,
                               guint64 start_time)
 {
-  PolkitUnixProcess *process;
-
-  process = POLKIT_UNIX_PROCESS (polkit_unix_process_new ((gint) -1));
-  process->pid = pid;
-  if (start_time != 0)
-    process->start_time = start_time;
-  else
-    process->start_time = get_start_time_for_pid (pid, NULL);
+  return POLKIT_SUBJECT (g_object_new (POLKIT_TYPE_UNIX_PROCESS,
+                                       "pid", pid,
+                                       "start_time", start_time,
+                                       NULL));
+}
 
-  return POLKIT_SUBJECT (process);
+/**
+ * polkit_unix_process_new_for_owner:
+ * @pid: The process id.
+ * @start_time: The start time for @pid or 0 to look it up in e.g. <filename>/proc</filename>.
+ * @uid: The (real, not effective) uid of the owner of @pid or -1 to look it up in e.g. <filename>/proc</filename>.
+ *
+ * Creates a new #PolkitUnixProcess object for @pid, @start_time and @uid.
+ *
+ * Returns: (transfer full): A #PolkitSubject. Free with g_object_unref().
+ */
+PolkitSubject *
+polkit_unix_process_new_for_owner (gint    pid,
+                                   guint64 start_time,
+                                   gint    uid)
+{
+  return POLKIT_SUBJECT (g_object_new (POLKIT_TYPE_UNIX_PROCESS,
+                                       "pid", pid,
+                                       "start_time", start_time,
+                                       "uid", uid,
+                                       NULL));
 }
 
 static guint
@@ -612,3 +656,95 @@ out:
 
   return start_time;
 }
+
+static gint
+_polkit_unix_process_get_owner (PolkitUnixProcess  *process,
+                                GError            **error)
+{
+  gint result;
+  gchar *contents;
+  gchar **lines;
+#ifdef HAVE_FREEBSD
+  struct kinfo_proc p;
+#else
+  gchar filename[64];
+  guint n;
+#endif
+
+  g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), 0);
+  g_return_val_if_fail (error == NULL || *error == NULL, 0);
+
+  result = 0;
+  lines = NULL;
+  contents = NULL;
+
+#ifdef HAVE_FREEBSD
+  if (get_kinfo_proc (process->pid, &p) == 0)
+    {
+      g_set_error (error,
+                   POLKIT_ERROR,
+                   POLKIT_ERROR_FAILED,
+                   "get_kinfo_proc() failed for pid %d: %s",
+                   process->pid,
+                   g_strerror (errno));
+      goto out;
+    }
+
+  result = p.ki_uid;
+#else
+
+  /* see 'man proc' for layout of the status file
+   *
+   * Uid, Gid: Real, effective, saved set,  and  file  system  UIDs (GIDs).
+   */
+  g_snprintf (filename, sizeof filename, "/proc/%d/status", process->pid);
+  if (!g_file_get_contents (filename,
+                            &contents,
+                            NULL,
+                            error))
+    {
+      goto out;
+    }
+  lines = g_strsplit (contents, "\n", -1);
+  for (n = 0; lines != NULL && lines[n] != NULL; n++)
+    {
+      gint real_uid, effective_uid;
+      if (!g_str_has_prefix (lines[n], "Uid:"))
+        continue;
+      if (sscanf (lines[n] + 4, "%d %d", &real_uid, &effective_uid) != 2)
+        {
+          g_set_error (error,
+                       POLKIT_ERROR,
+                       POLKIT_ERROR_FAILED,
+                       "Unexpected line `%s' in file %s",
+                       lines[n],
+                       filename);
+          goto out;
+        }
+      else
+        {
+          result = real_uid;
+          goto out;
+        }
+    }
+
+  g_set_error (error,
+               POLKIT_ERROR,
+               POLKIT_ERROR_FAILED,
+               "Didn't find any line starting with `Uid:' in file %s",
+               filename);
+#endif
+
+out:
+  g_strfreev (lines);
+  g_free (contents);
+  return result;
+}
+
+/* deprecated public method */
+gint
+polkit_unix_process_get_owner (PolkitUnixProcess  *process,
+                               GError            **error)
+{
+  return _polkit_unix_process_get_owner (process, error);
+}
diff --git a/src/polkit/polkitunixprocess.h b/src/polkit/polkitunixprocess.h
index b88cd03..68c7fd7 100644
--- a/src/polkit/polkitunixprocess.h
+++ b/src/polkit/polkitunixprocess.h
@@ -50,11 +50,21 @@ GType           polkit_unix_process_get_type       (void) G_GNUC_CONST;
 PolkitSubject  *polkit_unix_process_new            (gint pid);
 PolkitSubject  *polkit_unix_process_new_full       (gint pid,
                                                     guint64 start_time);
+PolkitSubject  *polkit_unix_process_new_for_owner  (gint    pid,
+                                                    guint64 start_time,
+                                                    gint    uid);
 
 gint            polkit_unix_process_get_pid        (PolkitUnixProcess *process);
+gint            polkit_unix_process_get_uid        (PolkitUnixProcess *process);
 guint64         polkit_unix_process_get_start_time (PolkitUnixProcess *process);
 void            polkit_unix_process_set_pid        (PolkitUnixProcess *process,
                                                     gint               pid);
+void            polkit_unix_process_set_uid        (PolkitUnixProcess *process,
+                                                    gint               uid);
+void            polkit_unix_process_set_start_time (PolkitUnixProcess *process,
+                                                    guint64            start_time);
+
+G_GNUC_DEPRECATED
 gint            polkit_unix_process_get_owner      (PolkitUnixProcess  *process,
                                                     GError            **error);
 
-- 
1.7.4.4

