Commit 45f52acb authored by David Zeuthen's avatar David Zeuthen

add support for negative authorizations

Negative authorizations is a way to block an entity; previously the
algorithm was something like (ignoring the config file for now)

  Result is_authorized() {
    res = has_implicit_auth();
    if (res == YES) {
      return YES;
    } else if (has_explicit_auth()) {
      return YES;
    }
    return res;
  }

Now it's

  Result is_authorized() {
    res = has_implicit_auth();
    expl = has_explicit_auth();
    is_blocked = has_negative_explicit_auth();

    if (is_blocked)
      return NO;

    if (res == YES) {
      return YES;
    } else if (has_explicit_auth()) {
      return YES;
    }
    return res;
  }

E.g. just a single negative auth will force NO to be returned. I
really, really need to write into the spec how this works; my mental
L1 cache can't contain it anymore. Once it's formally defined we need
to craft a test suite to verify that the code works according to
spec...
parent 8dd9f25b
......@@ -24,6 +24,7 @@
<arg><option><arg><option>--user <replaceable>user</replaceable></option></arg> --explicit</option></arg>
<arg><option><arg><option>--user <replaceable>user</replaceable></option></arg> --explicit-detail</option></arg>
<arg><option><arg><option>--user <replaceable>user</replaceable></option></arg> --grant <replaceable>action</replaceable></option><arg><option>--constraint <replaceable>constraint</replaceable></option></arg></arg>
<arg><option><arg><option>--user <replaceable>user</replaceable></option></arg> --block <replaceable>action</replaceable></option><arg><option>--constraint <replaceable>constraint</replaceable></option></arg></arg>
<arg><option><arg><option>--user <replaceable>user</replaceable></option></arg> --revoke <replaceable>action</replaceable></option></arg>
<arg><option>--version</option></arg>
<arg><option>--help</option></arg>
......@@ -109,6 +110,24 @@
</listitem>
</varlistentry>
<varlistentry>
<term><option><arg><option>--user <replaceable>user</replaceable></option></arg> --block <replaceable>action</replaceable></option><arg><option>--constraint <replaceable>constraint</replaceable></option></arg></term>
<listitem>
<para>
Grant an negative authorization for an action. Negative
authorizations are normally used to block users that would
normally be authorized due to implicit
authorizations. Optionally, a constraint on the granted
negative authorization can be specified; allowed values
are: <literal>local</literal>,
<literal>active</literal>, <literal>local+active</literal>.
The authorization needed to grant negative authorizations is
<literal>org.freedesktop.policykit.grant</literal> if the
"beneficiary" is another user.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option><arg><option>--user <replaceable>user</replaceable></option></arg> --revoke <replaceable>action</replaceable></option></term>
<listitem>
......
......@@ -157,6 +157,8 @@ polkit_check_authv (pid_t pid, const char **action_ids)
ret = 0;
errno = ENOENT;
context = NULL;
caller = NULL;
dbus_error_init (&error);
bus = dbus_bus_get (DBUS_BUS_SYSTEM, &error);
......
......@@ -533,6 +533,9 @@ polkit_authorization_db_add_entry_always (PolKitAuthorizationDB *authd
typedef struct {
char *action_id;
PolKitAuthorizationConstraint *constraint;
polkit_bool_t is_authorized;
polkit_bool_t is_negative_authorized;
} CheckDataGrant;
static polkit_bool_t
......@@ -540,6 +543,7 @@ _check_auth_for_grant (PolKitAuthorizationDB *authdb, PolKitAuthorization *auth,
{
uid_t pimp;
polkit_bool_t ret;
polkit_bool_t is_negative;
CheckDataGrant *cd = (CheckDataGrant *) user_data;
ret = FALSE;
......@@ -547,42 +551,34 @@ _check_auth_for_grant (PolKitAuthorizationDB *authdb, PolKitAuthorization *auth,
if (strcmp (polkit_authorization_get_action_id (auth), cd->action_id) != 0)
goto no_match;
if (!polkit_authorization_was_granted_explicitly (auth, &pimp))
if (!polkit_authorization_was_granted_explicitly (auth, &pimp, &is_negative))
goto no_match;
if (!polkit_authorization_constraint_equal (polkit_authorization_get_constraint (auth), cd->constraint))
goto no_match;
ret = TRUE;
if (is_negative) {
cd->is_authorized = FALSE;
cd->is_negative_authorized = TRUE;
/* it only takes a single negative auth to block things so stop iterating */
ret = TRUE;
} else {
cd->is_authorized = TRUE;
cd->is_negative_authorized = FALSE;
/* keep iterating; we may find negative auths... */
}
no_match:
return ret;
}
/**
* polkit_authorization_db_grant_to_uid:
* @authdb: authorization database
* @action: action
* @uid: uid to grant to
* @constraint: what constraint to put on the authorization
* @error: return location for error
*
* Grants an authorization to a user for a specific action. This
* requires the org.freedesktop.policykit.grant authorization.
*
* This function is in <literal>libpolkit-grant</literal>.
*
* Returns: #TRUE if the authorization was granted, #FALSE otherwise
* and error will be set
*
* Since: 0.7
*/
polkit_bool_t
polkit_authorization_db_grant_to_uid (PolKitAuthorizationDB *authdb,
PolKitAction *action,
uid_t uid,
PolKitAuthorizationConstraint *constraint,
PolKitError **error)
static polkit_bool_t
_grant_internal (PolKitAuthorizationDB *authdb,
PolKitAction *action,
uid_t uid,
PolKitAuthorizationConstraint *constraint,
PolKitError **error,
polkit_bool_t is_negative)
{
GError *g_error;
char *helper_argv[6] = {PACKAGE_LIBEXEC_DIR "/polkit-explicit-grant-helper", NULL, NULL, NULL, NULL, NULL};
......@@ -590,6 +586,7 @@ polkit_authorization_db_grant_to_uid (PolKitAuthorizationDB *authdb,
gint exit_status;
char cbuf[256];
CheckDataGrant cd;
polkit_bool_t did_exist;
ret = FALSE;
......@@ -614,16 +611,29 @@ polkit_authorization_db_grant_to_uid (PolKitAuthorizationDB *authdb,
/* check if we have the auth already */
cd.constraint = constraint;
if (!polkit_authorization_db_foreach_for_uid (authdb,
uid,
_check_auth_for_grant,
&cd,
error)) {
/* happens if caller can't read auths of target user */
if (error != NULL && polkit_error_is_set (*error)) {
goto out;
}
cd.is_authorized = FALSE;
cd.is_negative_authorized = FALSE;
polkit_authorization_db_foreach_for_uid (authdb,
uid,
_check_auth_for_grant,
&cd,
error);
/* happens if caller can't read auths of target user */
if (error != NULL && polkit_error_is_set (*error)) {
goto out;
}
did_exist = FALSE;
if (is_negative) {
if (cd.is_negative_authorized)
did_exist = TRUE;
} else {
if (cd.is_authorized)
did_exist = TRUE;
}
if (did_exist) {
/* so it did exist.. */
polkit_error_set_error (error,
POLKIT_ERROR_AUTHORIZATION_ALREADY_EXISTS,
......@@ -635,7 +645,10 @@ polkit_authorization_db_grant_to_uid (PolKitAuthorizationDB *authdb,
helper_argv[1] = cd.action_id;
helper_argv[2] = cbuf;
helper_argv[3] = "uid";
if (is_negative)
helper_argv[3] = "uid-negative";
else
helper_argv[3] = "uid";
helper_argv[4] = g_strdup_printf ("%d", uid);
helper_argv[5] = NULL;
......@@ -676,5 +689,65 @@ polkit_authorization_db_grant_to_uid (PolKitAuthorizationDB *authdb,
out:
g_free (helper_argv[4]);
return ret;
}
/**
* polkit_authorization_db_grant_to_uid:
* @authdb: authorization database
* @action: action
* @uid: uid to grant to
* @constraint: what constraint to put on the authorization
* @error: return location for error
*
* Grants an authorization to a user for a specific action. This
* requires the org.freedesktop.policykit.grant authorization.
*
* This function is in <literal>libpolkit-grant</literal>.
*
* Returns: #TRUE if the authorization was granted, #FALSE otherwise
* and error will be set
*
* Since: 0.7
*/
polkit_bool_t
polkit_authorization_db_grant_to_uid (PolKitAuthorizationDB *authdb,
PolKitAction *action,
uid_t uid,
PolKitAuthorizationConstraint *constraint,
PolKitError **error)
{
return _grant_internal (authdb, action, uid, constraint, error, FALSE);
}
/**
* polkit_authorization_db_grant_negative_to_uid:
* @authdb: authorization database
* @action: action
* @uid: uid to grant to
* @constraint: what constraint to put on the authorization
* @error: return location for error
*
* Grants a negative authorization to a user for a specific action. If
* @uid differs from the calling user, the
* org.freedesktop.policykit.grant authorization is required. In other
* words, users may "grant" negative authorizations to themselves.
*
* A negative authorization is normally used to block users that would
* normally be authorized from an implicit authorization.
*
* This function is in <literal>libpolkit-grant</literal>.
*
* Returns: #TRUE if the authorization was granted, #FALSE otherwise
* and error will be set
*
* Since: 0.7
*/
polkit_bool_t
polkit_authorization_db_grant_negative_to_uid (PolKitAuthorizationDB *authdb,
PolKitAction *action,
uid_t uid,
PolKitAuthorizationConstraint *constraint,
PolKitError **error)
{
return _grant_internal (authdb, action, uid, constraint, error, TRUE);
}
......@@ -124,9 +124,16 @@ main (int argc, char *argv[])
#define TARGET_UID 0
int target;
uid_t target_uid = -1;
polkit_bool_t is_negative;
/* (third, fourth) is one of: ("uid", uid) */
if (strcmp (argv[3], "uid") == 0) {
is_negative = FALSE;
/* (third, fourth) is one of: ("uid", uid), ("uid-negative", uid) */
if (strcmp (argv[3], "uid") == 0 || strcmp (argv[3], "uid-negative") == 0) {
if (strcmp (argv[3], "uid") != 0) {
is_negative = TRUE;
}
target = TARGET_UID;
target_uid = strtol (argv[4], &endp, 10);
......@@ -147,14 +154,19 @@ main (int argc, char *argv[])
/* OK, we're done parsing ... check if the user is authorized */
if (invoking_uid != 0) {
pid_t ppid;
ppid = getppid ();
if (ppid == 1)
goto out;
if (polkit_check_auth (ppid, "org.freedesktop.policykit.grant", NULL) == 0) {
goto out;
if (is_negative && (invoking_uid == target_uid)) {
/* it's fine to grant negative-auths to one self... */
} else {
pid_t ppid;
ppid = getppid ();
if (ppid == 1)
goto out;
if (polkit_check_auth (ppid, "org.freedesktop.policykit.grant", NULL) == 0) {
goto out;
}
}
}
......@@ -169,7 +181,8 @@ main (int argc, char *argv[])
if (snprintf (grant_line,
sizeof (grant_line),
"grant:%s:%Lu:%d:%s\n",
is_negative ? "grant-negative:%s:%Lu:%d:%s\n" :
"grant:%s:%Lu:%d:%s\n" ,
action_id,
(polkit_uint64_t) now.tv_sec,
invoking_uid,
......
......@@ -185,7 +185,8 @@ main (int argc, char *argv[])
root = PACKAGE_LOCALSTATE_DIR "/run/PolicyKit";
} else if (strcmp (scope, "always") == 0) {
root = PACKAGE_LOCALSTATE_DIR "/lib/PolicyKit";
} else if (strcmp (scope, "grant") == 0) {
} else if (strcmp (scope, "grant") == 0 ||
strcmp (scope, "grant-negative") == 0) {
uid_t granted_by;
root = PACKAGE_LOCALSTATE_DIR "/lib/PolicyKit";
......@@ -208,8 +209,10 @@ main (int argc, char *argv[])
}
if (invoking_uid != 0) {
/* Check that the caller is privileged to do this... */
if (not_granted_by_self || (invoking_uid != uid_to_revoke)) {
/* Check that the caller is privileged to do this... basically, callers can only
* revoke auths granted by themselves...
*/
if (not_granted_by_self) {
pid_t ppid;
ppid = getppid ();
......
......@@ -340,7 +340,10 @@ _authdb_get_auths_for_uid (PolKitAuthorizationDB *authdb,
auth = _polkit_authorization_new_for_uid (line, uid2);
if (auth != NULL) {
ret = kit_list_prepend (ret, auth);
/* we need the authorizations in the chronological order...
* (TODO: optimized: prepend, then reverse after all items have been inserted)
*/
ret = kit_list_append (ret, auth);
}
}
......@@ -540,12 +543,17 @@ typedef struct {
uid_t session_uid;
char *session_objpath;
PolKitSession *session;
polkit_bool_t *out_is_authorized;
polkit_bool_t *out_is_negative_authorized;
} CheckDataSession;
static polkit_bool_t
_check_auth_for_session (PolKitAuthorizationDB *authdb, PolKitAuthorization *auth, void *user_data)
{
polkit_bool_t ret;
uid_t pimp_uid;
polkit_bool_t is_negative;
CheckDataSession *cd = (CheckDataSession *) user_data;
PolKitAuthorizationConstraint *constraint;
......@@ -573,7 +581,29 @@ _check_auth_for_session (PolKitAuthorizationDB *authdb, PolKitAuthorization *aut
break;
}
ret = TRUE;
if (!polkit_authorization_was_granted_explicitly (auth, &pimp_uid, &is_negative))
is_negative = FALSE;
if (is_negative) {
*(cd->out_is_authorized) = FALSE;
*(cd->out_is_negative_authorized) = TRUE;
} else {
*(cd->out_is_authorized) = TRUE;
*(cd->out_is_negative_authorized) = FALSE;
}
/* keep iterating; we may find negative auths... */
if (is_negative) {
*(cd->out_is_authorized) = FALSE;
*(cd->out_is_negative_authorized) = TRUE;
/* it only takes a single negative auth to block things so stop iterating */
ret = TRUE;
} else {
*(cd->out_is_authorized) = TRUE;
*(cd->out_is_negative_authorized) = FALSE;
/* keep iterating; we may find negative auths... */
}
no_match:
return ret;
......@@ -585,9 +615,14 @@ no_match:
* @action: the action to check for
* @session: the session to check for
* @out_is_authorized: return location
* @out_is_negative_authorized: return location
*
* Looks in the authorization database and determine if processes from
* the given session are authorized to do the given specific action.
* the given session are authorized to do the given specific
* action. If there is an authorization record that matches the
* session, @out_is_authorized will be set to %TRUE. If there is a
* negative authorization record matching the session
* @out_is_negative_authorized will be set to %TRUE.
*
* Returns: #TRUE if the look up was performed; #FALSE if the caller
* of this function lacks privileges to ask this question (e.g. asking
......@@ -599,7 +634,8 @@ polkit_bool_t
polkit_authorization_db_is_session_authorized (PolKitAuthorizationDB *authdb,
PolKitAction *action,
PolKitSession *session,
polkit_bool_t *out_is_authorized)
polkit_bool_t *out_is_authorized,
polkit_bool_t *out_is_negative_authorized)
{
polkit_bool_t ret;
CheckDataSession cd;
......@@ -624,13 +660,17 @@ polkit_authorization_db_is_session_authorized (PolKitAuthorizationDB *authdb,
ret = TRUE;
cd.out_is_authorized = out_is_authorized;
cd.out_is_negative_authorized = out_is_negative_authorized;
*out_is_authorized = FALSE;
*out_is_negative_authorized = FALSE;
if (polkit_authorization_db_foreach_for_uid (authdb,
cd.session_uid,
_check_auth_for_session,
&cd,
NULL)) {
*out_is_authorized = TRUE;
;
}
return ret;
......@@ -644,13 +684,17 @@ typedef struct {
char *session_objpath;
PolKitCaller *caller;
polkit_bool_t revoke_if_one_shot;
polkit_bool_t *out_is_authorized;
polkit_bool_t *out_is_negative_authorized;
} CheckData;
static polkit_bool_t
_check_auth_for_caller (PolKitAuthorizationDB *authdb, PolKitAuthorization *auth, void *user_data)
{
polkit_bool_t ret;
uid_t pimp_uid;
polkit_bool_t is_negative;
pid_t caller_pid;
polkit_uint64_t caller_pid_start_time;
CheckData *cd = (CheckData *) user_data;
......@@ -701,7 +745,19 @@ _check_auth_for_caller (PolKitAuthorizationDB *authdb, PolKitAuthorization *auth
break;
}
ret = TRUE;
if (!polkit_authorization_was_granted_explicitly (auth, &pimp_uid, &is_negative))
is_negative = FALSE;
if (is_negative) {
*(cd->out_is_authorized) = FALSE;
*(cd->out_is_negative_authorized) = TRUE;
/* it only takes a single negative auth to block things so stop iterating */
ret = TRUE;
} else {
*(cd->out_is_authorized) = TRUE;
*(cd->out_is_negative_authorized) = FALSE;
/* keep iterating; we may find negative auths... */
}
no_match:
......@@ -716,9 +772,13 @@ no_match:
* @revoke_if_one_shot: Whether to revoke one-shot authorizations. See
* discussion in polkit_context_is_caller_authorized() for details.
* @out_is_authorized: return location
* @out_is_negative_authorized: return location
*
* Looks in the authorization database if the given caller is
* authorized to do the given action.
* authorized to do the given action. If there is an authorization
* record that matches the caller, @out_is_authorized will be set to
* %TRUE. If there is a negative authorization record matching the
* caller @out_is_negative_authorized will be set to %TRUE.
*
* Returns: #TRUE if the look up was performed; #FALSE if the caller
* of this function lacks privileges to ask this question (e.g. asking
......@@ -731,7 +791,8 @@ polkit_authorization_db_is_caller_authorized (PolKitAuthorizationDB *authdb,
PolKitAction *action,
PolKitCaller *caller,
polkit_bool_t revoke_if_one_shot,
polkit_bool_t *out_is_authorized)
polkit_bool_t *out_is_authorized,
polkit_bool_t *out_is_negative_authorized)
{
PolKitSession *session;
polkit_bool_t ret;
......@@ -769,13 +830,17 @@ polkit_authorization_db_is_caller_authorized (PolKitAuthorizationDB *authdb,
ret = TRUE;
cd.out_is_authorized = out_is_authorized;
cd.out_is_negative_authorized = out_is_negative_authorized;
*out_is_authorized = FALSE;
*out_is_negative_authorized = FALSE;
if (polkit_authorization_db_foreach_for_uid (authdb,
cd.caller_uid,
_check_auth_for_caller,
&cd,
NULL)) {
*out_is_authorized = TRUE;
;
}
return ret;
......@@ -849,6 +914,77 @@ out:
return ret;
}
static polkit_bool_t
_check_self_block_foreach (PolKitAuthorizationDB *authdb,
PolKitAuthorization *auth,
void *user_data)
{
polkit_bool_t *is_self_blocked = (polkit_bool_t *) user_data;
polkit_bool_t is_negative;
uid_t pimp_uid;
polkit_bool_t ret;
if (!polkit_authorization_was_granted_explicitly (auth, &pimp_uid, &is_negative))
is_negative = FALSE;
if (is_negative) {
if (pimp_uid == getuid ()) {
*is_self_blocked = TRUE;
/* can't stop iterating.. there may be another one who blocked us too! */
} else {
*is_self_blocked = FALSE;
ret = TRUE;
/* nope; someone else blocked us.. that's enough to ruin it */
}
}
return ret;
}
/**
* polkit_authorization_db_is_uid_blocked_by_self:
* @authdb: the authorization database
* @action: the action to check for
* @uid: the user to check for
* @error: return location for error
*
* Determine whether there exists negative authorizations for the
* particular uid on the given action and whether those negative
* authorization are "granted" by the uid itself.
*
* If uid is different from getuid(), e.g. if the calling process asks
* for auths of another user this function will set an error if the
* calling user is not authorized for org.freedesktop.policykit.read.
*
* Returns: Result of computation described above; if error is set
* will return %FALSE.
*
* Since: 0.7
*/
polkit_bool_t
polkit_authorization_db_is_uid_blocked_by_self (PolKitAuthorizationDB *authdb,
PolKitAction *action,
uid_t uid,
PolKitError **error)
{
polkit_bool_t is_self_blocked;
kit_return_val_if_fail (authdb != NULL, FALSE);
kit_return_val_if_fail (action != NULL, FALSE);
is_self_blocked = FALSE;
polkit_authorization_db_foreach_for_action_for_uid (authdb,
action,
uid,
_check_self_block_foreach,
&is_self_blocked,
error);
return is_self_blocked;
}
#ifdef POLKIT_BUILD_TESTS
static polkit_bool_t
......
......@@ -68,13 +68,15 @@ polkit_bool_t polkit_authorization_db_validate (PolKitAuthorizati
polkit_bool_t polkit_authorization_db_is_session_authorized (PolKitAuthorizationDB *authdb,
PolKitAction *action,
PolKitSession *session,
polkit_bool_t *out_is_authorized);
polkit_bool_t *out_is_authorized,
polkit_bool_t *out_is_negative_authorized);
polkit_bool_t polkit_authorization_db_is_caller_authorized (PolKitAuthorizationDB *authdb,
PolKitAction *action,
PolKitCaller *caller,
polkit_bool_t revoke_if_one_shot,
polkit_bool_t *out_is_authorized);
polkit_bool_t *out_is_authorized,
polkit_bool_t *out_is_negative_authorized);
/**
* PolKitAuthorizationDBForeach:
......@@ -144,11 +146,22 @@ polkit_bool_t polkit_authorization_db_grant_to_uid (PolKitAuthorizatio
PolKitAuthorizationConstraint *constraint,
PolKitError **error);
polkit_bool_t polkit_authorization_db_grant_negative_to_uid (PolKitAuthorizationDB *authdb,
PolKitAction *action,
uid_t uid,
PolKitAuthorizationConstraint *constraint,
PolKitError **error);
polkit_bool_t polkit_authorization_db_revoke_entry (PolKitAuthorizationDB *authdb,