/* SELinux access controls for nscd. Copyright (C) 2004, 2005, 2006, 2007 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Matthew Rickard , 2004. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBAUDIT # include #endif #include "dbg_log.h" #include "selinux.h" #ifdef HAVE_SELINUX /* Global variable to tell if the kernel has SELinux support. */ int selinux_enabled; /* Define mappings of access vector permissions to request types. */ static const int perms[LASTREQ] = { [GETPWBYNAME] = NSCD__GETPWD, [GETPWBYUID] = NSCD__GETPWD, [GETGRBYNAME] = NSCD__GETGRP, [GETGRBYGID] = NSCD__GETGRP, [GETHOSTBYNAME] = NSCD__GETHOST, [GETHOSTBYNAMEv6] = NSCD__GETHOST, [GETHOSTBYADDR] = NSCD__GETHOST, [GETHOSTBYADDRv6] = NSCD__GETHOST, [GETSTAT] = NSCD__GETSTAT, [SHUTDOWN] = NSCD__ADMIN, [INVALIDATE] = NSCD__ADMIN, [GETFDPW] = NSCD__SHMEMPWD, [GETFDGR] = NSCD__SHMEMGRP, [GETFDHST] = NSCD__SHMEMHOST, [GETAI] = NSCD__GETHOST, [INITGROUPS] = NSCD__GETGRP, #ifdef NSCD__GETSERV [GETSERVBYNAME] = NSCD__GETSERV, [GETSERVBYPORT] = NSCD__GETSERV, [GETFDSERV] = NSCD__SHMEMSERV, #endif }; /* Store an entry ref to speed AVC decisions. */ static struct avc_entry_ref aeref; /* Thread to listen for SELinux status changes via netlink. */ static pthread_t avc_notify_thread; #ifdef HAVE_LIBAUDIT /* Prototype for supporting the audit daemon */ static void log_callback (const char *fmt, ...); #endif /* Prototypes for AVC callback functions. */ static void *avc_create_thread (void (*run) (void)); static void avc_stop_thread (void *thread); static void *avc_alloc_lock (void); static void avc_get_lock (void *lock); static void avc_release_lock (void *lock); static void avc_free_lock (void *lock); /* AVC callback structures for use in avc_init. */ static const struct avc_log_callback log_cb = { #ifdef HAVE_LIBAUDIT .func_log = log_callback, #else .func_log = dbg_log, #endif .func_audit = NULL }; static const struct avc_thread_callback thread_cb = { .func_create_thread = avc_create_thread, .func_stop_thread = avc_stop_thread }; static const struct avc_lock_callback lock_cb = { .func_alloc_lock = avc_alloc_lock, .func_get_lock = avc_get_lock, .func_release_lock = avc_release_lock, .func_free_lock = avc_free_lock }; #ifdef HAVE_LIBAUDIT /* The audit system's netlink socket descriptor */ static int audit_fd = -1; /* When an avc denial occurs, log it to audit system */ static void log_callback (const char *fmt, ...) { if (audit_fd >= 0) { va_list ap; va_start (ap, fmt); char *buf; int e = vasprintf (&buf, fmt, ap); if (e < 0) { buf = alloca (BUFSIZ); vsnprintf (buf, BUFSIZ, fmt, ap); } /* FIXME: need to attribute this to real user, using getuid for now */ audit_log_user_avc_message (audit_fd, AUDIT_USER_AVC, buf, NULL, NULL, NULL, getuid ()); if (e >= 0) free (buf); va_end (ap); } } /* Initialize the connection to the audit system */ static void audit_init (void) { audit_fd = audit_open (); if (audit_fd < 0 /* If kernel doesn't support audit, bail out */ && errno != EINVAL && errno != EPROTONOSUPPORT && errno != EAFNOSUPPORT) dbg_log (_("Failed opening connection to the audit subsystem: %m")); } # ifdef HAVE_LIBCAP static const cap_value_t new_cap_list[] = { CAP_AUDIT_WRITE }; # define nnew_cap_list (sizeof (new_cap_list) / sizeof (new_cap_list[0])) static const cap_value_t tmp_cap_list[] = { CAP_AUDIT_WRITE, CAP_SETUID, CAP_SETGID }; # define ntmp_cap_list (sizeof (tmp_cap_list) / sizeof (tmp_cap_list[0])) cap_t preserve_capabilities (void) { if (getuid () != 0) /* Not root, then we cannot preserve anything. */ return NULL; if (prctl (PR_SET_KEEPCAPS, 1) == -1) { dbg_log (_("Failed to set keep-capabilities")); error (EXIT_FAILURE, errno, _("prctl(KEEPCAPS) failed")); /* NOTREACHED */ } cap_t tmp_caps = cap_init (); cap_t new_caps; if (tmp_caps != NULL) new_caps = cap_init (); if (tmp_caps == NULL || new_caps == NULL) { if (tmp_caps != NULL) cap_free (tmp_caps); dbg_log (_("Failed to initialize drop of capabilities")); error (EXIT_FAILURE, 0, _("cap_init failed")); } /* There is no reason why these should not work. */ cap_set_flag (new_caps, CAP_PERMITTED, nnew_cap_list, (cap_value_t *) new_cap_list, CAP_SET); cap_set_flag (new_caps, CAP_EFFECTIVE, nnew_cap_list, (cap_value_t *) new_cap_list, CAP_SET); cap_set_flag (tmp_caps, CAP_PERMITTED, ntmp_cap_list, (cap_value_t *) tmp_cap_list, CAP_SET); cap_set_flag (tmp_caps, CAP_EFFECTIVE, ntmp_cap_list, (cap_value_t *) tmp_cap_list, CAP_SET); int res = cap_set_proc (tmp_caps); cap_free (tmp_caps); if (__builtin_expect (res != 0, 0)) { cap_free (new_caps); dbg_log (_("Failed to drop capabilities")); error (EXIT_FAILURE, 0, _("cap_set_proc failed")); } return new_caps; } void install_real_capabilities (cap_t new_caps) { /* If we have no capabilities there is nothing to do here. */ if (new_caps == NULL) return; if (cap_set_proc (new_caps)) { cap_free (new_caps); dbg_log (_("Failed to drop capabilities")); error (EXIT_FAILURE, 0, _("cap_set_proc failed")); /* NOTREACHED */ } cap_free (new_caps); if (prctl (PR_SET_KEEPCAPS, 0) == -1) { dbg_log (_("Failed to unset keep-capabilities")); error (EXIT_FAILURE, errno, _("prctl(KEEPCAPS) failed")); /* NOTREACHED */ } } # endif /* HAVE_LIBCAP */ #endif /* HAVE_LIBAUDIT */ /* Determine if we are running on an SELinux kernel. Set selinux_enabled to the result. */ void nscd_selinux_enabled (int *selinux_enabled) { *selinux_enabled = is_selinux_enabled (); if (*selinux_enabled < 0) { dbg_log (_("Failed to determine if kernel supports SELinux")); exit (EXIT_FAILURE); } } /* Create thread for AVC netlink notification. */ static void * avc_create_thread (void (*run) (void)) { int rc; rc = pthread_create (&avc_notify_thread, NULL, (void *(*) (void *)) run, NULL); if (rc != 0) error (EXIT_FAILURE, rc, _("Failed to start AVC thread")); return &avc_notify_thread; } /* Stop AVC netlink thread. */ static void avc_stop_thread (void *thread) { pthread_cancel (*(pthread_t *) thread); } /* Allocate a new AVC lock. */ static void * avc_alloc_lock (void) { pthread_mutex_t *avc_mutex; avc_mutex = malloc (sizeof (pthread_mutex_t)); if (avc_mutex == NULL) error (EXIT_FAILURE, errno, _("Failed to create AVC lock")); pthread_mutex_init (avc_mutex, NULL); return avc_mutex; } /* Acquire an AVC lock. */ static void avc_get_lock (void *lock) { pthread_mutex_lock (lock); } /* Release an AVC lock. */ static void avc_release_lock (void *lock) { pthread_mutex_unlock (lock); } /* Free an AVC lock. */ static void avc_free_lock (void *lock) { pthread_mutex_destroy (lock); free (lock); } /* Initialize the user space access vector cache (AVC) for NSCD along with log/thread/lock callbacks. */ void nscd_avc_init (void) { avc_entry_ref_init (&aeref); if (avc_init ("avc", NULL, &log_cb, &thread_cb, &lock_cb) < 0) error (EXIT_FAILURE, errno, _("Failed to start AVC")); else dbg_log (_("Access Vector Cache (AVC) started")); #ifdef HAVE_LIBAUDIT audit_init (); #endif } /* Check the permission from the caller (via getpeercon) to nscd. Returns 0 if access is allowed, 1 if denied, and -1 on error. */ int nscd_request_avc_has_perm (int fd, request_type req) { /* Initialize to NULL so we know what to free in case of failure. */ security_context_t scon = NULL; security_context_t tcon = NULL; security_id_t ssid = NULL; security_id_t tsid = NULL; int rc = -1; if (getpeercon (fd, &scon) < 0) { dbg_log (_("Error getting context of socket peer")); goto out; } if (getcon (&tcon) < 0) { dbg_log (_("Error getting context of nscd")); goto out; } if (avc_context_to_sid (scon, &ssid) < 0 || avc_context_to_sid (tcon, &tsid) < 0) { dbg_log (_("Error getting sid from context")); goto out; } rc = avc_has_perm (ssid, tsid, SECCLASS_NSCD, perms[req], &aeref, NULL) < 0; out: if (scon) freecon (scon); if (tcon) freecon (tcon); if (ssid) sidput (ssid); if (tsid) sidput (tsid); return rc; } /* Wrapper to get AVC statistics. */ void nscd_avc_cache_stats (struct avc_cache_stats *cstats) { avc_cache_stats (cstats); } /* Print the AVC statistics to stdout. */ void nscd_avc_print_stats (struct avc_cache_stats *cstats) { printf (_("\nSELinux AVC Statistics:\n\n" "%15u entry lookups\n" "%15u entry hits\n" "%15u entry misses\n" "%15u entry discards\n" "%15u CAV lookups\n" "%15u CAV hits\n" "%15u CAV probes\n" "%15u CAV misses\n"), cstats->entry_lookups, cstats->entry_hits, cstats->entry_misses, cstats->entry_discards, cstats->cav_lookups, cstats->cav_hits, cstats->cav_probes, cstats->cav_misses); } /* Clean up the AVC before exiting. */ void nscd_avc_destroy (void) { avc_destroy (); #ifdef HAVE_LIBAUDIT audit_close (audit_fd); #endif } #endif /* HAVE_SELINUX */