Commit message: Fix CVE-2018-1116: Trusting client-supplied UID

As part of CVE-2013-4288, the D-Bus clients were allowed (and
encouraged) to submit the UID of the subject of authorization checks
to avoid races against UID changes (notably using executables
set-UID to root).

However, that also allowed any client to submit an arbitrary UID, and
that could be used to bypass "can only ask about / affect the same UID"
checks in CheckAuthorization / RegisterAuthenticationAgent /
UnregisterAuthenticationAgent.  This allowed an attacker:

- With CheckAuthorization, to cause the registered authentication
  agent in victim's session to pop up a dialog, or to determine whether
  the victim currently has a temporary authorization to perform an
  operation.

  (In principle, the attacker can also determine whether JavaScript
  rules allow the victim process to perform an operation; however,
  usually rules base their decisions on information determined from
  the supplied UID, so the attacker usually won't learn anything new.)

- With RegisterAuthenticationAgent, to prevent the victim's
  authentication agent to work (for a specific victim process),
  or to learn about which operations requiring authorization
  the victim is attempting.

To fix this, expose internal _polkit_unix_process_get_owner() /
obsolete polkit_unix_process_get_owner() as a private
polkit_unix_process_get_racy_uid__() (being more explicit about the
dangers on relying on it), and use it in
polkit_backend_session_monitor_get_user_for_subject() to return
a boolean indicating whether the subject UID may be caller-chosen.

Then, in the permission checks that require the subject to be
equal to the caller, fail on caller-chosen UIDs (and continue
through the pre-existing code paths which allow root, or root-designated
server processes, to ask about arbitrary subjects.)

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
CVE: CVE-2018-1116
CWE: CWE-200
Vulnerability description: A flaw was found in polkit before version 0.116. The implementation of the polkit_backend_interactive_authority_check_authorization function in polkitd allows to test for authentication and trigger authentication of unrelated processes owned by other users. This may result in a local DoS and information disclosure.

Project: polkit
File: polkitbackendinteractiveauthority.c

log_result (PolkitBackendInteractiveAuthority    *authority,
            const gchar                          *action_id,
            PolkitSubject                        *subject,
            PolkitSubject                        *caller,
            PolkitAuthorizationResult            *result)
{
  PolkitBackendInteractiveAuthorityPrivate *priv;
  PolkitIdentity *user_of_subject;
  const gchar *log_result_str;
  gchar *subject_str;
  gchar *user_of_subject_str;
  gchar *caller_str;
  gchar *subject_cmdline;
  gchar *caller_cmdline;

  priv = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_GET_PRIVATE (authority);

  log_result_str = "DENYING";
   if (polkit_authorization_result_get_is_authorized (result))
     log_result_str = "ALLOWING";
 
  user_of_subject = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor, subject, NULL, NULL);
 
   subject_str = polkit_subject_to_string (subject);
 
  if (user_of_subject != NULL)
    user_of_subject_str = polkit_identity_to_string (user_of_subject);
  else
    user_of_subject_str = g_strdup ("<unknown>");
  caller_str = polkit_subject_to_string (caller);

  subject_cmdline = _polkit_subject_get_cmdline (subject);
  if (subject_cmdline == NULL)
    subject_cmdline = g_strdup ("<unknown>");

  caller_cmdline = _polkit_subject_get_cmdline (caller);
  if (caller_cmdline == NULL)
    caller_cmdline = g_strdup ("<unknown>");

  polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
                                "%s action %s for %s [%s] owned by %s (check requested by %s [%s])",
                                log_result_str,
                                action_id,
                                subject_str,
                                subject_cmdline,
                                user_of_subject_str,
                                caller_str,
                                caller_cmdline);

  if (user_of_subject != NULL)
    g_object_unref (user_of_subject);
  g_free (subject_str);
  g_free (user_of_subject_str);
  g_free (caller_str);
  g_free (subject_cmdline);
  g_free (caller_cmdline);
}

Project: polkit

polkit_backend_interactive_authority_check_authorization (PolkitBackendAuthority         *authority,
                                                          PolkitSubject                  *caller,
                                                          PolkitSubject                  *subject,
                                                          const gchar                    *action_id,
                                                          PolkitDetails                  *details,
                                                          PolkitCheckAuthorizationFlags   flags,
                                                          GCancellable                   *cancellable,
                                                          GAsyncReadyCallback             callback,
                                                          gpointer                        user_data)
{
  PolkitBackendInteractiveAuthority *interactive_authority;
  PolkitBackendInteractiveAuthorityPrivate *priv;
  gchar *caller_str;
   gchar *subject_str;
   PolkitIdentity *user_of_caller;
   PolkitIdentity *user_of_subject;
  gboolean user_of_subject_matches;
   gchar *user_of_caller_str;
   gchar *user_of_subject_str;
   PolkitAuthorizationResult *result;
  GError *error;
  GSimpleAsyncResult *simple;
  gboolean has_details;
  gchar **detail_keys;

  interactive_authority = POLKIT_BACKEND_INTERACTIVE_AUTHORITY (authority);
  priv = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_GET_PRIVATE (interactive_authority);

  error = NULL;
  caller_str = NULL;
  subject_str = NULL;
  user_of_caller = NULL;
  user_of_subject = NULL;
  user_of_caller_str = NULL;
  user_of_subject_str = NULL;
  result = NULL;

  simple = g_simple_async_result_new (G_OBJECT (authority),
                                      callback,
                                      user_data,
                                      polkit_backend_interactive_authority_check_authorization);

  /* handle being called from ourselves */
  if (caller == NULL)
    {
      /* TODO: this is kind of a hack */
      GDBusConnection *system_bus;
      system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL);
      caller = polkit_system_bus_name_new (g_dbus_connection_get_unique_name (system_bus));
      g_object_unref (system_bus);
    }

  caller_str = polkit_subject_to_string (caller);
  subject_str = polkit_subject_to_string (subject);

  g_debug ("%s is inquiring whether %s is authorized for %s",
           caller_str,
           subject_str,
           action_id);
            action_id);
 
   user_of_caller = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor,
                                                                        caller, NULL,
                                                                         &error);
   if (error != NULL)
     {
      g_simple_async_result_complete (simple);
      g_object_unref (simple);
      g_error_free (error);
      goto out;
    }

  user_of_caller_str = polkit_identity_to_string (user_of_caller);
  g_debug (" user of caller is %s", user_of_caller_str);
   g_debug (" user of caller is %s", user_of_caller_str);
 
   user_of_subject = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor,
                                                                         subject, &user_of_subject_matches,
                                                                          &error);
   if (error != NULL)
     {
      g_simple_async_result_complete (simple);
      g_object_unref (simple);
      g_error_free (error);
      goto out;
    }

  user_of_subject_str = polkit_identity_to_string (user_of_subject);
  g_debug (" user of subject is %s", user_of_subject_str);

  has_details = FALSE;
  if (details != NULL)
    {
      detail_keys = polkit_details_get_keys (details);
      if (detail_keys != NULL)
        {
          if (g_strv_length (detail_keys) > 0)
            has_details = TRUE;
          g_strfreev (detail_keys);
        }
    }

  /* Not anyone is allowed to check that process XYZ is allowed to do ABC.
   * We only allow this if, and only if,
    * We only allow this if, and only if,
    *
    *  - processes may check for another process owned by the *same* user but not
   *    if details are passed (otherwise you'd be able to spoof the dialog);
   *    the caller supplies the user_of_subject value, so we additionally
   *    require it to match at least at one point in time (via
   *    user_of_subject_matches).
    *
    *  - processes running as uid 0 may check anything and pass any details
    *
  if (!polkit_identity_equal (user_of_caller, user_of_subject) || has_details)
    *    then any uid referenced by that annotation is also allowed to check
    *    to check anything and pass any details
    */
  if (!user_of_subject_matches
      || !polkit_identity_equal (user_of_caller, user_of_subject)
      || has_details)
     {
       if (!may_identity_check_authorization (interactive_authority, action_id, user_of_caller))
         {
                                               "pass details");
            }
          else
            {
              g_simple_async_result_set_error (simple,
                                               POLKIT_ERROR,
                                               POLKIT_ERROR_NOT_AUTHORIZED,
                                               "Only trusted callers (e.g. uid 0 or an action owner) can use CheckAuthorization() for "
                                               "subjects belonging to other identities");
            }
          g_simple_async_result_complete (simple);
          g_object_unref (simple);
          goto out;
        }
    }

Project: polkit

 polkit_backend_session_monitor_get_user_for_subject (PolkitBackendSessionMonitor  *monitor,
                                                      PolkitSubject                *subject,
                                                     gboolean                     *result_matches,
                                                      GError                      **error)
 {
   PolkitIdentity *ret;
  gboolean matches;
 
   ret = NULL;
  matches = FALSE;
 
   if (POLKIT_IS_UNIX_PROCESS (subject))
     {
      gint subject_uid, current_uid;
      GError *local_error;

      subject_uid = polkit_unix_process_get_uid (POLKIT_UNIX_PROCESS (subject));
      if (subject_uid == -1)
         {
           g_set_error (error,
                        POLKIT_ERROR,
          g_set_error (error,
                        "Unix process subject does not have uid set");
           goto out;
         }
      local_error = NULL;
      current_uid = polkit_unix_process_get_racy_uid__ (POLKIT_UNIX_PROCESS (subject), &local_error);
      if (local_error != NULL)
	{
	  g_propagate_error (error, local_error);
	  goto out;
	}
      ret = polkit_unix_user_new (subject_uid);
      matches = (subject_uid == current_uid);
     }
   else if (POLKIT_IS_SYSTEM_BUS_NAME (subject))
     {
       ret = (PolkitIdentity*)polkit_system_bus_name_get_user_sync (POLKIT_SYSTEM_BUS_NAME (subject), NULL, error);
      matches = TRUE;
     }
   else if (POLKIT_IS_UNIX_SESSION (subject))
     {
      uid_t uid;
 
       if (sd_session_get_uid (polkit_unix_session_get_session_id (POLKIT_UNIX_SESSION (subject)), &uid) < 0)
         {
polkit_backend_session_monitor_get_session_for_subject (PolkitBackendSessionMonitor *monitor,
                                                        PolkitSubject               *subject,
                                                        GError                     **error)
{
  PolkitUnixProcess *tmp_process = NULL;
         }
 
       ret = polkit_unix_user_new (uid);
      matches = TRUE;
     }
 
  out:
  if (result_matches != NULL)
    {
      *result_matches = matches;
    }
   return ret;
 }
      if (!tmp_process)
	goto out;
      process = tmp_process;
    }