Commit a8e46ceb authored by David Zeuthen's avatar David Zeuthen
Browse files

add constraints for exe and SELinux context when granting an authorization

The way it works is that added constraints now look like this

scope=always:action-id=org.pulseaudio.acquire-high-priority:when=1197004781:auth-as=0:constraint=local:constraint=active:constraint=exe%3A%2Fusr%2Fbin%2Fpulseaudio:constraint=selinux_context%3Asystem_u%3Asystem_r%3Aunconfined_t

or if not using SELinux like this

scope=always:action-id=org.freedesktop.hal.storage.mount-fixed:when=1197008218:auth-as=0:constraint=local:constraint=active:constraint=exe%3A%2Fusr%2Fbin%2Fgnome-mount

This is a bit icky to implement for mechanisms, like HAL, running as
an unprivileged user. The problem is that we can't resolve the symlink
/proc/pid/exe. On the other hands such mechanisms has the
authorization org.freedesktop.policykit.read already. So use that.

Note that this is what some people call snake-oil. The reason is in the
docs for polkit_sysdeps_get_pid_for_exe(); copying it here so I can point
people to this commit in the future

  Get the name of the binary a given process was started from.

  Note that this is not necessary reliable information and as such
  shouldn't be relied on 100% to make a security decision. In fact,
  this information is only trustworthy in situations where the given
  binary is securely locked down meaning that 1) it can't be
  ptrace(2)'d; 2) libc secure mode kicks in (e.g LD_PRELOAD won't
  work); 3) there are no other attack vectors (e.g. GTK_MODULES, X11,
  CORBA, D-Bus) to patch running code into the process.

  In other words: the risk of relying on constraining an authorization
  to the output of this function is high. Suppose that the program
  /usr/bin/gullible obtains an authorization via authentication for
  the action org.example.foo. We add a constraint to say that the
  gained authorization only applies to processes for whom
  /proc/pid/exe points to /usr/bin/gullible. Now enter
  /usr/bin/evil. It knows that the program /usr/bin/gullible is not
  "securely locked down" (per the definition in the above
  paragraph). So /usr/bin/evil simply sets LD_PRELOAD and execs
  /usr/bin/gullible and it can now run code in a process where
  /proc/pid/exe points to /usr/bin/gullible. Thus, the recently gained
  authorization for org.example.foo applies. Also, /usr/bin/evil could
  use a host of other attack vectors to run it's own code under the
  disguise of pretending to be /usr/bin/gullible.

  Specifically for interpreted languages like Python and Mono it is
  the case that /proc/pid/exe always points to /usr/bin/python
  resp. /usr/bin/mono. Thus, it's not very useful to rely on that the
  result for this function if you want to constrain an authorization
  to e.g. /usr/bin/tomboy or /usr/bin/banshee.

However. Once we have a framework for running secure desktop apps this
will start to make sense. Such a framework includes securing X (using
e.g. XACE with SELinux) and making the UI toolkit secure as well. It's
a lot of work.

Until then these constraints at least makes it harder to for malicious
apps to abuse PolicyKit authorizations gained by other users.
parent 0bb7eeac
......@@ -54,3 +54,7 @@
- Include the patch from Piter PUNK to optionally avoid the PAM
dependency (manually checks against /etc/shadow instead)
- To avoid work we should maintain a cache in the get_exe_for_pid()
functions. The key into the cache should be (pid, pid_start_time)
and the values should be the exe-paths
......@@ -29,8 +29,14 @@ libpolkit_dbus_la_LIBADD = @DBUS_LIBS@ $(top_builddir)/src/polkit/libpolkit.la $
libpolkit_dbus_la_LDFLAGS = -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) @R_DYNAMIC_LDFLAG@
libexec_PROGRAMS = polkit-resolve-exe-helper
polkit_resolve_exe_helper_SOURCES = polkit-resolve-exe-helper.c
polkit_resolve_exe_helper_CFLAGS = @DBUS_CFLAGS@
polkit_resolve_exe_helper_LDADD = $(top_builddir)/src/polkit/libpolkit.la libpolkit-dbus.la
if POLKIT_AUTHDB_DEFAULT
libexec_PROGRAMS = polkit-read-auth-helper polkit-set-default-helper
libexec_PROGRAMS += polkit-read-auth-helper polkit-set-default-helper
polkit_read_auth_helper_SOURCES = polkit-read-auth-helper.c
polkit_read_auth_helper_CFLAGS = @DBUS_CFLAGS@
......@@ -47,13 +53,21 @@ polkit_set_default_helper_LDADD = $(top_builddir)/src/polkit/libpolkit.la libpol
# polkit-set-default-helper needs to be setgid $POLKIT_GROUP to be able
# to write .override files in /var/lib/PolicyKit-public
#
# polkit-resolve-exe-helper needs to be setuid root to be able to resolve
# /proc/$pid/exe symlinks.
#
install-exec-hook:
-chgrp $(POLKIT_GROUP) $(DESTDIR)$(libexecdir)/polkit-read-auth-helper
-chmod 2755 $(DESTDIR)$(libexecdir)/polkit-read-auth-helper
-chgrp $(POLKIT_GROUP) $(DESTDIR)$(libexecdir)/polkit-set-default-helper
-chmod 2755 $(DESTDIR)$(libexecdir)/polkit-set-default-helper
-chmod 4755 $(DESTDIR)$(libexecdir)/polkit-resolve-exe-helper
else
install-exec-hook:
-chmod 4755 $(DESTDIR)$(libexecdir)/polkit-resolve-exe-helper
endif
## note that TESTS has special meaning (stuff to use in make check)
## so if adding tests not to be run in make check, don't add them to
## TESTS
......
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
/***************************************************************************
*
* polkit-resolve-exe-helper.c : setuid root helper for PolicyKit to
* resolve /proc/$pid/exe symlinks
*
* Copyright (C) 2007 David Zeuthen, <david@fubar.dk>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
**************************************************************************/
#define _GNU_SOURCE
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <security/pam_appl.h>
#include <grp.h>
#include <pwd.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
#include <utime.h>
#include <fcntl.h>
#include <dirent.h>
#include <polkit-dbus/polkit-dbus.h>
#include <polkit/polkit-private.h>
int
main (int argc, char *argv[])
{
int ret;
uid_t caller_uid;
pid_t requesting_info_for_pid;
char *endp;
uid_t uid_for_polkit_user;
struct passwd *pw;
gid_t egid;
struct group *group;
int n;
char buf[PATH_MAX];
polkit_bool_t is_setgid_polkit;
ret = 1;
/* clear the entire environment to avoid attacks using with libraries honoring environment variables */
if (clearenv () != 0)
goto out;
/* set a minimal environment */
setenv ("PATH", "/usr/sbin:/usr/bin:/sbin:/bin", 1);
openlog ("polkit-resolve-exe-helper", LOG_CONS | LOG_PID, LOG_AUTHPRIV);
/* check for correct invocation */
if (argc != 2) {
syslog (LOG_NOTICE, "inappropriate use of helper, wrong number of arguments [uid=%d]", getuid ());
fprintf (stderr, "polkit-resolve-exe-helper: wrong number of arguments. This incident has been logged.\n");
goto out;
}
caller_uid = getuid ();
/* check we're running with a non-tty stdin */
if (isatty (STDIN_FILENO) != 0) {
syslog (LOG_NOTICE, "inappropriate use of helper, stdin is a tty [uid=%d]", getuid ());
fprintf (stderr, "polkit-resolve-exe-helper: inappropriate use of helper, stdin is a tty. This incident has been logged.\n");
goto out;
}
pw = getpwnam (POLKIT_USER);
if (pw == NULL) {
fprintf (stderr, "polkit-resolve-exe-helper: cannot lookup uid for " POLKIT_USER "\n");
goto out;
}
uid_for_polkit_user = pw->pw_uid;
/* check if we are setgid polkituser */
egid = getegid ();
group = getgrgid (egid);
if (group == NULL) {
fprintf (stderr, "polkit-resolve-exe-helper: cannot lookup group info for gid %d\n", egid);
goto out;
}
if (strcmp (group->gr_name, POLKIT_GROUP) == 0) {
is_setgid_polkit = TRUE;
} else {
is_setgid_polkit = FALSE;
}
/*----------------------------------------------------------------------------------------------------*/
requesting_info_for_pid = strtoul (argv[1], &endp, 10);
if (*endp != '\0') {
fprintf (stderr, "polkit-resolve-exe-helper: requesting_info_for_pid malformed\n");
goto out;
}
/* user polkituser is allowed to resolve anything. So is any program that is setgid polkituser. */
if (caller_uid != uid_for_polkit_user && !is_setgid_polkit) {
pid_t ppid;
ppid = getppid ();
if (ppid == 1)
goto out;
/* need to set the real uid of the process to root ... otherwise D-Bus won't work */
if (setuid (0) != 0) {
fprintf (stderr, "polkit-resolve-exe-helper: cannot do setuid(0): %m\n");
goto out;
}
if (polkit_check_auth (ppid,
"org.freedesktop.policykit.read", NULL) == 0) {
fprintf (stderr, "polkit-resolve-exe-helper: not authorized for org.freedesktop.policykit.read\n");
goto out;
}
}
n = polkit_sysdeps_get_exe_for_pid (requesting_info_for_pid, buf, sizeof (buf));
if (n == -1 || n >= (int) sizeof (buf)) {
fprintf (stderr, "polkit-resolve-exe-helper: Cannot resolve link for pid %d\n",
requesting_info_for_pid);
goto out;
}
printf ("%s", buf);
ret = 0;
out:
return ret;
}
......@@ -267,14 +267,17 @@ static polkit_bool_t
_add_caller_constraints (char *buf, size_t buf_size, PolKitCaller *caller)
{
PolKitAuthorizationConstraint *constraints[64];
size_t num_constraints;
int num_constraints;
polkit_bool_t ret;
int num_written;
unsigned int n;
int n;
ret = FALSE;
num_constraints = polkit_authorization_constraint_get_from_caller (caller, constraints, 64);
if (num_constraints == -1)
goto out;
if (num_constraints >= 64) {
goto out;
}
......
......@@ -49,13 +49,23 @@ POLKIT_BEGIN_DECLS
* caller must be local
* @POLKIT_AUTHORIZATION_CONSTRAINT_TYPE_REQUIRE_ACTIVE: the session or
* caller must be in an active session
* @POLKIT_AUTHORIZATION_CONSTRAINT_TYPE_REQUIRE_EXE: the caller must
* be a specific program; use
* polkit_authorization_constraint_get_exe() to get the path of the
* program.
* @POLKIT_AUTHORIZATION_CONSTRAINT_TYPE_REQUIRE_SELINUX_CONTEXT: the
* caller must be in a specific security context
* polkit_authorization_constraint_get_selinux_context() to get the
* security context.
*
* This enumeration describes the type of the authorization
* constraint.
*/
typedef enum {
POLKIT_AUTHORIZATION_CONSTRAINT_TYPE_REQUIRE_LOCAL,
POLKIT_AUTHORIZATION_CONSTRAINT_TYPE_REQUIRE_ACTIVE
POLKIT_AUTHORIZATION_CONSTRAINT_TYPE_REQUIRE_ACTIVE,
POLKIT_AUTHORIZATION_CONSTRAINT_TYPE_REQUIRE_EXE,
POLKIT_AUTHORIZATION_CONSTRAINT_TYPE_REQUIRE_SELINUX_CONTEXT,
} PolKitAuthorizationConstraintType;
struct _PolKitAuthorizationConstraint;
......@@ -63,6 +73,8 @@ typedef struct _PolKitAuthorizationConstraint PolKitAuthorizationConstraint;
PolKitAuthorizationConstraint *polkit_authorization_constraint_get_require_local (void);
PolKitAuthorizationConstraint *polkit_authorization_constraint_get_require_active (void);
PolKitAuthorizationConstraint *polkit_authorization_constraint_get_require_exe (const char *path);
PolKitAuthorizationConstraint *polkit_authorization_constraint_get_require_selinux_context (const char *context);
PolKitAuthorizationConstraint *polkit_authorization_constraint_ref (PolKitAuthorizationConstraint *authc);
void polkit_authorization_constraint_unref (PolKitAuthorizationConstraint *authc);
......@@ -71,6 +83,10 @@ polkit_bool_t polkit_authorization_constraint_validate (PolKitA
PolKitAuthorizationConstraintType polkit_authorization_constraint_type (PolKitAuthorizationConstraint *authc);
const char *polkit_authorization_constraint_get_exe (PolKitAuthorizationConstraint *authc);
const char *polkit_authorization_constraint_get_selinux_context (PolKitAuthorizationConstraint *authc);
polkit_bool_t polkit_authorization_constraint_check_session (PolKitAuthorizationConstraint *authc,
PolKitSession *session);
......@@ -80,7 +96,7 @@ polkit_bool_t polkit_authorization_constraint_check_caller (PolKitAuthorizationC
size_t polkit_authorization_constraint_to_string (PolKitAuthorizationConstraint *authc, char *out_buf, size_t buf_size);
PolKitAuthorizationConstraint *polkit_authorization_constraint_from_string (const char *str);
size_t polkit_authorization_constraint_get_from_caller (PolKitCaller *caller, PolKitAuthorizationConstraint **out_array, size_t array_size);
int polkit_authorization_constraint_get_from_caller (PolKitCaller *caller, PolKitAuthorizationConstraint **out_array, size_t array_size);
polkit_bool_t polkit_authorization_constraint_equal (PolKitAuthorizationConstraint *a,
PolKitAuthorizationConstraint *b);
......
......@@ -55,8 +55,6 @@ void _polkit_memory_fail_nth_alloc (int number);
PolKitAuthorization *_polkit_authorization_new_for_uid (const char *entry_in_auth_file, uid_t uid);
const char *_polkit_authorization_get_authfile_entry (PolKitAuthorization *auth);
PolKitAuthorizationConstraint *_polkit_authorization_constraint_new (const char *entry_in_auth_file);
polkit_bool_t _polkit_authorization_db_auth_file_add (polkit_bool_t transient, uid_t uid, char *str_to_add);
PolKitAuthorizationDB *_polkit_authorization_db_new (void);
......
......@@ -35,6 +35,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
......@@ -131,10 +132,51 @@ out:
* @out_buf: buffer to store the string representation in
* @buf_size: size of buffer
*
* Get the name of the binary a given process was started from. Note
* that this is not reliable information; it should not be part of any
* security decision. If the information could not be obtained 0 is
* returned and out_buf will be set to "(unknown)".
* Get the name of the binary a given process was started from.
*
* Note that this is not necessary reliable information and as such
* shouldn't be relied on 100% to make a security decision. In fact,
* this information is only trustworthy in situations where the given
* binary is securely locked down meaning that 1) it can't be
* <literal>ptrace(2)</literal>'d; 2) libc secure mode kicks in (e.g
* <literal>LD_PRELOAD</literal> won't work); 3) there are no other
* attack vectors (e.g. GTK_MODULES, X11, CORBA, D-Bus) to patch
* running code into the process.
*
* In other words: the risk of relying on constraining an
* authorization to the output of this function is high. Suppose that
* the program <literal>/usr/bin/gullible</literal> obtains an
* authorization via authentication for the action
* <literal>org.example.foo</literal>. We add a constraint to say that
* the gained authorization only applies to processes for whom
* <literal>/proc/pid/exe</literal> points to
* <literal>/usr/bin/gullible</literal>. Now enter
* <literal>/usr/bin/evil</literal>. It knows that the program
* <literal>/usr/bin/gullible</literal> is not "securely locked down"
* (per the definition in the above paragraph). So
* <literal>/usr/bin/evil</literal> simply sets
* <literal>LD_PRELOAD</literal> and execs
* <literal>/usr/bin/gullible</literal> and it can now run code in a
* process where <literal>/proc/pid/exe</literal> points to
* <literal>/usr/bin/gullible</literal>. Thus, the recently gained
* authorization for <literal>org.example.foo</literal> applies. Also,
* <literal>/usr/bin/evil</literal> could use a host of other attack
* vectors to run it's own code under the disguise of pretending to be
* <literal>/usr/bin/gullible</literal>.
*
* Specifically for interpreted languages like Python and Mono it is
* the case that <literal>/proc/pid/exe</literal> always points to
* <literal>/usr/bin/python</literal>
* resp. <literal>/usr/bin/mono</literal>. Thus, it's not very useful
* to rely on that the result for this function if you want to
* constrain an authorization to
* e.g. <literal>/usr/bin/tomboy</literal> or
* <literal>/usr/bin/banshee</literal>.
*
* If the information could not be obtained, such as if the given
* process is owned by another user than the caller, -1 is returned
* and out_buf will be set to "(unknown)". See also the function
* polkit_sysdeps_get_exe_for_pid_with_helper().
*
* Returns: Number of characters written (not including trailing
* '\0'). If the output was truncated due to the buffer being too
......@@ -151,6 +193,11 @@ polkit_sysdeps_get_exe_for_pid (pid_t pid, char *out_buf, size_t buf_size)
int ret;
char proc_name[32];
/* TODO: to avoid work we should maintain a cache. The key
* into the cache should be (pid, pid_start_time) and the
* values should be the exe-paths
*/
ret = 0;
snprintf (proc_name, sizeof (proc_name), "/proc/%d/exe", pid);
......@@ -166,6 +213,86 @@ out:
return ret;
}
/**
* polkit_sysdeps_get_exe_for_pid_with_helper:
* @pid: process id
* @out_buf: buffer to store the string representation in
* @buf_size: size of buffer
*
* Like polkit_sysdeps_get_exe_for_pid() but if the given process is
* owned by another user, a setuid root helper is used to obtain the
* information. This helper only works if 1) the caller is authorized
* for the org.freedesktop.policykit.read authorization; or 2) the
* calling user is polkituser; or 3) the calling user is setegid
* polkituser.
*
* So -1 might still be returned (the process might also have exited).
*
* Returns: See polkit_sysdeps_get_exe_for_pid().
*
* Since: 0.8
*/
int
polkit_sysdeps_get_exe_for_pid_with_helper (pid_t pid, char *out_buf, size_t buf_size)
{
int ret;
/* TODO: to avoid work we should maintain a cache. The key
* into the cache should be (pid, pid_start_time) and the
* values should be the exe-paths
*/
ret = polkit_sysdeps_get_exe_for_pid (pid, out_buf, buf_size);
if (ret == -1) {
char buf[32];
char *helper_argv[3] = {PACKAGE_LIBEXEC_DIR "/polkit-resolve-exe-helper", buf, NULL};
char *standard_output;
int exit_status;
/* Uh uh.. This means that we don't have permission to read /proc/$pid/exe for
* the given process id... this can happen if the mechanism in question runs
* as an unprivileged user instead of uid 0 (e.g. user 'haldaemon').
*
* This blows.
*
* To work around this we use a setuid root helper that
*
* 1. checks whether the caller (us) has the 1) org.freedesktop.policykit.read
* authorization; or 2) is $POLKIT_USER; or 3) is group $POLKIT_USER
*
* 2. If so, resolves /prod/$pid/exe and writes it to stdout
*/
snprintf (buf, sizeof (buf), "%d", pid);
if (!kit_spawn_sync (NULL, /* const char *working_directory */
0, /* flags */
helper_argv, /* char **argv */
NULL, /* char **envp */
NULL, /* char *stdin */
&standard_output, /* char **stdout */
NULL, /* char **stderr */
&exit_status)) { /* int *exit_status */
goto out;
}
if (!WIFEXITED (exit_status)) {
kit_warning ("resolve exe helper crashed!");
goto out;
} else if (WEXITSTATUS(exit_status) != 0) {
goto out;
}
strncpy (out_buf, standard_output, buf_size);
out_buf[buf_size - 1] = '\0';
ret = strlen (standard_output);
}
out:
return ret;
}
#ifdef POLKIT_BUILD_TESTS
static polkit_bool_t
......
......@@ -43,6 +43,8 @@ polkit_uint64_t polkit_sysdeps_get_start_time_for_pid (pid_t pid);
int polkit_sysdeps_get_exe_for_pid (pid_t pid, char *out_buf, size_t buf_size);
int polkit_sysdeps_get_exe_for_pid_with_helper (pid_t pid, char *out_buf, size_t buf_size);
POLKIT_END_DECLS
......
......@@ -397,6 +397,14 @@ _print_constraint (PolKitAuthorization *auth, PolKitAuthorizationConstraint *aut
case POLKIT_AUTHORIZATION_CONSTRAINT_TYPE_REQUIRE_ACTIVE:
printf (" Constraint: Session must be active\n");
break;
case POLKIT_AUTHORIZATION_CONSTRAINT_TYPE_REQUIRE_EXE:
printf (" Constraint: Only allowed for program %s\n",
polkit_authorization_constraint_get_exe (authc));
break;
case POLKIT_AUTHORIZATION_CONSTRAINT_TYPE_REQUIRE_SELINUX_CONTEXT:
printf (" Constraint: Only allowed for SELinux Context %s\n",
polkit_authorization_constraint_get_selinux_context (authc));
break;
}
return FALSE;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment