Blame nscd/selinux.c

Packit 6c4009
/* SELinux access controls for nscd.
Packit 6c4009
   Copyright (C) 2004-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
   Contributed by Matthew Rickard <mjricka@epoch.ncsc.mil>, 2004.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include "config.h"
Packit 6c4009
#include <error.h>
Packit 6c4009
#include <errno.h>
Packit 6c4009
#include <libintl.h>
Packit 6c4009
#include <pthread.h>
Packit 6c4009
#include <stdarg.h>
Packit 6c4009
#include <stdio.h>
Packit 6c4009
#include <stdlib.h>
Packit 6c4009
#include <syslog.h>
Packit 6c4009
#include <unistd.h>
Packit 6c4009
#include <sys/prctl.h>
Packit 6c4009
#include <selinux/avc.h>
Packit 6c4009
#include <selinux/selinux.h>
Packit 6c4009
#ifdef HAVE_LIBAUDIT
Packit 6c4009
# include <libaudit.h>
Packit 6c4009
#endif
Packit 6c4009
Packit 6c4009
#include "dbg_log.h"
Packit 6c4009
#include "selinux.h"
Packit 6c4009
Packit 6c4009
Packit 6c4009
#ifdef HAVE_SELINUX
Packit 6c4009
/* Global variable to tell if the kernel has SELinux support.  */
Packit 6c4009
int selinux_enabled;
Packit 6c4009
Packit 6c4009
/* Define mappings of request type to AVC permission name.  */
Packit 6c4009
static const char *perms[LASTREQ] =
Packit 6c4009
{
Packit 6c4009
  [GETPWBYNAME] = "getpwd",
Packit 6c4009
  [GETPWBYUID] = "getpwd",
Packit 6c4009
  [GETGRBYNAME] = "getgrp",
Packit 6c4009
  [GETGRBYGID] = "getgrp",
Packit 6c4009
  [GETHOSTBYNAME] = "gethost",
Packit 6c4009
  [GETHOSTBYNAMEv6] = "gethost",
Packit 6c4009
  [GETHOSTBYADDR] = "gethost",
Packit 6c4009
  [GETHOSTBYADDRv6] = "gethost",
Packit 6c4009
  [SHUTDOWN] = "admin",
Packit 6c4009
  [GETSTAT] = "getstat",
Packit 6c4009
  [INVALIDATE] = "admin",
Packit 6c4009
  [GETFDPW] = "shmempwd",
Packit 6c4009
  [GETFDGR] = "shmemgrp",
Packit 6c4009
  [GETFDHST] = "shmemhost",
Packit 6c4009
  [GETAI] = "gethost",
Packit 6c4009
  [INITGROUPS] = "getgrp",
Packit 6c4009
  [GETSERVBYNAME] = "getserv",
Packit 6c4009
  [GETSERVBYPORT] = "getserv",
Packit 6c4009
  [GETFDSERV] = "shmemserv",
Packit 6c4009
  [GETNETGRENT] = "getnetgrp",
Packit 6c4009
  [INNETGR] = "getnetgrp",
Packit 6c4009
  [GETFDNETGR] = "shmemnetgrp",
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
/* Store an entry ref to speed AVC decisions.  */
Packit 6c4009
static struct avc_entry_ref aeref;
Packit 6c4009
Packit 6c4009
/* Thread to listen for SELinux status changes via netlink.  */
Packit 6c4009
static pthread_t avc_notify_thread;
Packit 6c4009
Packit 6c4009
#ifdef HAVE_LIBAUDIT
Packit 6c4009
/* Prototype for supporting the audit daemon */
Packit 6c4009
static void log_callback (const char *fmt, ...);
Packit 6c4009
#endif
Packit 6c4009
Packit 6c4009
/* Prototypes for AVC callback functions.  */
Packit 6c4009
static void *avc_create_thread (void (*run) (void));
Packit 6c4009
static void avc_stop_thread (void *thread);
Packit 6c4009
static void *avc_alloc_lock (void);
Packit 6c4009
static void avc_get_lock (void *lock);
Packit 6c4009
static void avc_release_lock (void *lock);
Packit 6c4009
static void avc_free_lock (void *lock);
Packit 6c4009
Packit 6c4009
/* AVC callback structures for use in avc_init.  */
Packit 6c4009
static const struct avc_log_callback log_cb =
Packit 6c4009
{
Packit 6c4009
#ifdef HAVE_LIBAUDIT
Packit 6c4009
  .func_log = log_callback,
Packit 6c4009
#else
Packit 6c4009
  .func_log = dbg_log,
Packit 6c4009
#endif
Packit 6c4009
  .func_audit = NULL
Packit 6c4009
};
Packit 6c4009
static const struct avc_thread_callback thread_cb =
Packit 6c4009
{
Packit 6c4009
  .func_create_thread = avc_create_thread,
Packit 6c4009
  .func_stop_thread = avc_stop_thread
Packit 6c4009
};
Packit 6c4009
static const struct avc_lock_callback lock_cb =
Packit 6c4009
{
Packit 6c4009
  .func_alloc_lock = avc_alloc_lock,
Packit 6c4009
  .func_get_lock = avc_get_lock,
Packit 6c4009
  .func_release_lock = avc_release_lock,
Packit 6c4009
  .func_free_lock = avc_free_lock
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
#ifdef HAVE_LIBAUDIT
Packit 6c4009
/* The audit system's netlink socket descriptor */
Packit 6c4009
static int audit_fd = -1;
Packit 6c4009
Packit 6c4009
/* When an avc denial occurs, log it to audit system */
Packit 6c4009
static void
Packit 6c4009
log_callback (const char *fmt, ...)
Packit 6c4009
{
Packit 6c4009
  if (audit_fd >= 0)
Packit 6c4009
    {
Packit 6c4009
      va_list ap;
Packit 6c4009
      va_start (ap, fmt);
Packit 6c4009
Packit 6c4009
      char *buf;
Packit 6c4009
      int e = vasprintf (&buf, fmt, ap);
Packit 6c4009
      if (e < 0)
Packit 6c4009
	{
Packit 6c4009
	  buf = alloca (BUFSIZ);
Packit 6c4009
	  vsnprintf (buf, BUFSIZ, fmt, ap);
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
      /* FIXME: need to attribute this to real user, using getuid for now */
Packit 6c4009
      audit_log_user_avc_message (audit_fd, AUDIT_USER_AVC, buf, NULL, NULL,
Packit 6c4009
				  NULL, getuid ());
Packit 6c4009
Packit 6c4009
      if (e >= 0)
Packit 6c4009
	free (buf);
Packit 6c4009
Packit 6c4009
      va_end (ap);
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Initialize the connection to the audit system */
Packit 6c4009
static void
Packit 6c4009
audit_init (void)
Packit 6c4009
{
Packit 6c4009
  audit_fd = audit_open ();
Packit 6c4009
  if (audit_fd < 0
Packit 6c4009
      /* If kernel doesn't support audit, bail out */
Packit 6c4009
      && errno != EINVAL && errno != EPROTONOSUPPORT && errno != EAFNOSUPPORT)
Packit 6c4009
    dbg_log (_("Failed opening connection to the audit subsystem: %m"));
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
# ifdef HAVE_LIBCAP
Packit 6c4009
static const cap_value_t new_cap_list[] =
Packit 6c4009
  { CAP_AUDIT_WRITE };
Packit 6c4009
#  define nnew_cap_list (sizeof (new_cap_list) / sizeof (new_cap_list[0]))
Packit 6c4009
static const cap_value_t tmp_cap_list[] =
Packit 6c4009
  { CAP_AUDIT_WRITE, CAP_SETUID, CAP_SETGID };
Packit 6c4009
#  define ntmp_cap_list (sizeof (tmp_cap_list) / sizeof (tmp_cap_list[0]))
Packit 6c4009
Packit 6c4009
cap_t
Packit 6c4009
preserve_capabilities (void)
Packit 6c4009
{
Packit 6c4009
  if (getuid () != 0)
Packit 6c4009
    /* Not root, then we cannot preserve anything.  */
Packit 6c4009
    return NULL;
Packit 6c4009
Packit 6c4009
  if (prctl (PR_SET_KEEPCAPS, 1) == -1)
Packit 6c4009
    {
Packit 6c4009
      dbg_log (_("Failed to set keep-capabilities"));
Packit 6c4009
      do_exit (EXIT_FAILURE, errno, _("prctl(KEEPCAPS) failed"));
Packit 6c4009
      /* NOTREACHED */
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  cap_t tmp_caps = cap_init ();
Packit 6c4009
  cap_t new_caps = NULL;
Packit 6c4009
  if (tmp_caps != NULL)
Packit 6c4009
    new_caps = cap_init ();
Packit 6c4009
Packit 6c4009
  if (tmp_caps == NULL || new_caps == NULL)
Packit 6c4009
    {
Packit 6c4009
      if (tmp_caps != NULL)
Packit 6c4009
	cap_free (tmp_caps);
Packit 6c4009
Packit 6c4009
      dbg_log (_("Failed to initialize drop of capabilities"));
Packit 6c4009
      do_exit (EXIT_FAILURE, 0, _("cap_init failed"));
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* There is no reason why these should not work.  */
Packit 6c4009
  cap_set_flag (new_caps, CAP_PERMITTED, nnew_cap_list,
Packit 6c4009
		(cap_value_t *) new_cap_list, CAP_SET);
Packit 6c4009
  cap_set_flag (new_caps, CAP_EFFECTIVE, nnew_cap_list,
Packit 6c4009
		(cap_value_t *) new_cap_list, CAP_SET);
Packit 6c4009
Packit 6c4009
  cap_set_flag (tmp_caps, CAP_PERMITTED, ntmp_cap_list,
Packit 6c4009
		(cap_value_t *) tmp_cap_list, CAP_SET);
Packit 6c4009
  cap_set_flag (tmp_caps, CAP_EFFECTIVE, ntmp_cap_list,
Packit 6c4009
		(cap_value_t *) tmp_cap_list, CAP_SET);
Packit 6c4009
Packit 6c4009
  int res = cap_set_proc (tmp_caps);
Packit 6c4009
Packit 6c4009
  cap_free (tmp_caps);
Packit 6c4009
Packit 6c4009
  if (__glibc_unlikely (res != 0))
Packit 6c4009
    {
Packit 6c4009
      cap_free (new_caps);
Packit 6c4009
      dbg_log (_("Failed to drop capabilities"));
Packit 6c4009
      do_exit (EXIT_FAILURE, 0, _("cap_set_proc failed"));
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  return new_caps;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
install_real_capabilities (cap_t new_caps)
Packit 6c4009
{
Packit 6c4009
  /* If we have no capabilities there is nothing to do here.  */
Packit 6c4009
  if (new_caps == NULL)
Packit 6c4009
    return;
Packit 6c4009
Packit 6c4009
  if (cap_set_proc (new_caps))
Packit 6c4009
    {
Packit 6c4009
      cap_free (new_caps);
Packit 6c4009
      dbg_log (_("Failed to drop capabilities"));
Packit 6c4009
      do_exit (EXIT_FAILURE, 0, _("cap_set_proc failed"));
Packit 6c4009
      /* NOTREACHED */
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  cap_free (new_caps);
Packit 6c4009
Packit 6c4009
  if (prctl (PR_SET_KEEPCAPS, 0) == -1)
Packit 6c4009
    {
Packit 6c4009
      dbg_log (_("Failed to unset keep-capabilities"));
Packit 6c4009
      do_exit (EXIT_FAILURE, errno, _("prctl(KEEPCAPS) failed"));
Packit 6c4009
      /* NOTREACHED */
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
# endif /* HAVE_LIBCAP */
Packit 6c4009
#endif /* HAVE_LIBAUDIT */
Packit 6c4009
Packit 6c4009
/* Determine if we are running on an SELinux kernel. Set selinux_enabled
Packit 6c4009
   to the result.  */
Packit 6c4009
void
Packit 6c4009
nscd_selinux_enabled (int *selinux_enabled)
Packit 6c4009
{
Packit 6c4009
  *selinux_enabled = is_selinux_enabled ();
Packit 6c4009
  if (*selinux_enabled < 0)
Packit 6c4009
    {
Packit 6c4009
      dbg_log (_("Failed to determine if kernel supports SELinux"));
Packit 6c4009
      do_exit (EXIT_FAILURE, 0, NULL);
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Create thread for AVC netlink notification.  */
Packit 6c4009
static void *
Packit 6c4009
avc_create_thread (void (*run) (void))
Packit 6c4009
{
Packit 6c4009
  int rc;
Packit 6c4009
Packit 6c4009
  rc =
Packit 6c4009
    pthread_create (&avc_notify_thread, NULL, (void *(*) (void *)) run, NULL);
Packit 6c4009
  if (rc != 0)
Packit 6c4009
    do_exit (EXIT_FAILURE, rc, _("Failed to start AVC thread"));
Packit 6c4009
Packit 6c4009
  return &avc_notify_thread;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Stop AVC netlink thread.  */
Packit 6c4009
static void
Packit 6c4009
avc_stop_thread (void *thread)
Packit 6c4009
{
Packit 6c4009
  pthread_cancel (*(pthread_t *) thread);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Allocate a new AVC lock.  */
Packit 6c4009
static void *
Packit 6c4009
avc_alloc_lock (void)
Packit 6c4009
{
Packit 6c4009
  pthread_mutex_t *avc_mutex;
Packit 6c4009
Packit 6c4009
  avc_mutex = malloc (sizeof (pthread_mutex_t));
Packit 6c4009
  if (avc_mutex == NULL)
Packit 6c4009
    do_exit (EXIT_FAILURE, errno, _("Failed to create AVC lock"));
Packit 6c4009
  pthread_mutex_init (avc_mutex, NULL);
Packit 6c4009
Packit 6c4009
  return avc_mutex;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Acquire an AVC lock.  */
Packit 6c4009
static void
Packit 6c4009
avc_get_lock (void *lock)
Packit 6c4009
{
Packit 6c4009
  pthread_mutex_lock (lock);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Release an AVC lock.  */
Packit 6c4009
static void
Packit 6c4009
avc_release_lock (void *lock)
Packit 6c4009
{
Packit 6c4009
  pthread_mutex_unlock (lock);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Free an AVC lock.  */
Packit 6c4009
static void
Packit 6c4009
avc_free_lock (void *lock)
Packit 6c4009
{
Packit 6c4009
  pthread_mutex_destroy (lock);
Packit 6c4009
  free (lock);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Initialize the user space access vector cache (AVC) for NSCD along with
Packit 6c4009
   log/thread/lock callbacks.  */
Packit 6c4009
void
Packit 6c4009
nscd_avc_init (void)
Packit 6c4009
{
Packit 6c4009
  avc_entry_ref_init (&aeref);
Packit 6c4009
Packit 6c4009
  if (avc_init ("avc", NULL, &log_cb, &thread_cb, &lock_cb) < 0)
Packit 6c4009
    do_exit (EXIT_FAILURE, errno, _("Failed to start AVC"));
Packit 6c4009
  else
Packit 6c4009
    dbg_log (_("Access Vector Cache (AVC) started"));
Packit 6c4009
#ifdef HAVE_LIBAUDIT
Packit 6c4009
  audit_init ();
Packit 6c4009
#endif
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Check the permission from the caller (via getpeercon) to nscd.
Packit 6c4009
   Returns 0 if access is allowed, 1 if denied, and -1 on error.
Packit 6c4009
Packit 6c4009
   The SELinux policy, enablement, and permission bits are all dynamic and the
Packit 6c4009
   caching done by glibc is not entirely correct.  This nscd support should be
Packit 6c4009
   rewritten to use selinux_check_permission.  A rewrite is risky though and
Packit 6c4009
   requires some refactoring.  Currently we use symbolic mappings instead of
Packit 6c4009
   compile time constants (which SELinux upstream says are going away), and we
Packit 6c4009
   use security_deny_unknown to determine what to do if selinux-policy* doesn't
Packit 6c4009
   have a definition for the the permission or object class we are looking
Packit 6c4009
   up.  */
Packit 6c4009
int
Packit 6c4009
nscd_request_avc_has_perm (int fd, request_type req)
Packit 6c4009
{
Packit 6c4009
  /* Initialize to NULL so we know what to free in case of failure.  */
Packit 6c4009
  security_context_t scon = NULL;
Packit 6c4009
  security_context_t tcon = NULL;
Packit 6c4009
  security_id_t ssid = NULL;
Packit 6c4009
  security_id_t tsid = NULL;
Packit 6c4009
  int rc = -1;
Packit 6c4009
  security_class_t sc_nscd;
Packit 6c4009
  access_vector_t perm;
Packit 6c4009
  int avc_deny_unknown;
Packit 6c4009
Packit 6c4009
  /* Check if SELinux denys or allows unknown object classes
Packit 6c4009
     and permissions.  It is 0 if they are allowed, 1 if they
Packit 6c4009
     are not allowed and -1 on error.  */
Packit 6c4009
  if ((avc_deny_unknown = security_deny_unknown ()) == -1)
Packit 6c4009
    dbg_log (_("Error querying policy for undefined object classes "
Packit 6c4009
	       "or permissions."));
Packit 6c4009
Packit 6c4009
  /* Get the security class for nscd.  If this fails we will likely be
Packit 6c4009
     unable to do anything unless avc_deny_unknown is 0.  */
Packit 6c4009
  sc_nscd = string_to_security_class ("nscd");
Packit 6c4009
  if (sc_nscd == 0 && avc_deny_unknown == 1)
Packit 6c4009
    dbg_log (_("Error getting security class for nscd."));
Packit 6c4009
Packit 6c4009
  /* Convert permission to AVC bits.  */
Packit 6c4009
  perm = string_to_av_perm (sc_nscd, perms[req]);
Packit 6c4009
  if (perm == 0 && avc_deny_unknown == 1)
Packit 6c4009
      dbg_log (_("Error translating permission name "
Packit 6c4009
		 "\"%s\" to access vector bit."), perms[req]);
Packit 6c4009
Packit 6c4009
  /* If the nscd security class was not found or perms were not
Packit 6c4009
     found and AVC does not deny unknown values then allow it.  */
Packit 6c4009
  if ((sc_nscd == 0 || perm == 0) && avc_deny_unknown == 0)
Packit 6c4009
    return 0;
Packit 6c4009
Packit 6c4009
  if (getpeercon (fd, &scon) < 0)
Packit 6c4009
    {
Packit 6c4009
      dbg_log (_("Error getting context of socket peer"));
Packit 6c4009
      goto out;
Packit 6c4009
    }
Packit 6c4009
  if (getcon (&tcon) < 0)
Packit 6c4009
    {
Packit 6c4009
      dbg_log (_("Error getting context of nscd"));
Packit 6c4009
      goto out;
Packit 6c4009
    }
Packit 6c4009
  if (avc_context_to_sid (scon, &ssid) < 0
Packit 6c4009
      || avc_context_to_sid (tcon, &tsid) < 0)
Packit 6c4009
    {
Packit 6c4009
      dbg_log (_("Error getting sid from context"));
Packit 6c4009
      goto out;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* The SELinux API for avc_has_perm conflates access denied and error into
Packit 6c4009
     the return code -1, while nscd_request_avs_has_perm has distinct error
Packit 6c4009
     (-1) and denied (1) return codes. We map the avc_has_perm access denied or
Packit 6c4009
     error into an access denied at the nscd interface level (we do accurately
Packit 6c4009
     report error for the getpeercon, getcon, and avc_context_to_sid interfaces
Packit 6c4009
     used above).  */
Packit 6c4009
  rc = avc_has_perm (ssid, tsid, sc_nscd, perm, &aeref, NULL) < 0;
Packit 6c4009
Packit 6c4009
out:
Packit 6c4009
  if (scon)
Packit 6c4009
    freecon (scon);
Packit 6c4009
  if (tcon)
Packit 6c4009
    freecon (tcon);
Packit 6c4009
  if (ssid)
Packit 6c4009
    sidput (ssid);
Packit 6c4009
  if (tsid)
Packit 6c4009
    sidput (tsid);
Packit 6c4009
Packit 6c4009
  return rc;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Wrapper to get AVC statistics.  */
Packit 6c4009
void
Packit 6c4009
nscd_avc_cache_stats (struct avc_cache_stats *cstats)
Packit 6c4009
{
Packit 6c4009
  avc_cache_stats (cstats);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Print the AVC statistics to stdout.  */
Packit 6c4009
void
Packit 6c4009
nscd_avc_print_stats (struct avc_cache_stats *cstats)
Packit 6c4009
{
Packit 6c4009
  printf (_("\nSELinux AVC Statistics:\n\n"
Packit 6c4009
	    "%15u  entry lookups\n"
Packit 6c4009
	    "%15u  entry hits\n"
Packit 6c4009
	    "%15u  entry misses\n"
Packit 6c4009
	    "%15u  entry discards\n"
Packit 6c4009
	    "%15u  CAV lookups\n"
Packit 6c4009
	    "%15u  CAV hits\n"
Packit 6c4009
	    "%15u  CAV probes\n"
Packit 6c4009
	    "%15u  CAV misses\n"),
Packit 6c4009
	  cstats->entry_lookups, cstats->entry_hits, cstats->entry_misses,
Packit 6c4009
	  cstats->entry_discards, cstats->cav_lookups, cstats->cav_hits,
Packit 6c4009
	  cstats->cav_probes, cstats->cav_misses);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
#endif /* HAVE_SELINUX */