mirror of
https://sourceware.org/git/glibc.git
synced 2024-11-29 08:11:08 +00:00
532a60357e
Currently, the nscd parent process parses commandline options and configuration, forks on startup and immediately exits with a success. If the child process encounters some error after this, it goes undetected and any services started up after it may have to repeatedly check to make sure that the nscd service did actually start up and is serving requests. To make this process more reliable, I have added a pipe between the parent and child process, through which the child process sends a notification to the parent informing it of its status. The parent waits for this status and once it receives it, exits with the corresponding exit code. So if the child service sends a success status (0), the parent exits with a success status. Similarly for error conditions, the child sends the non-zero status code, which the parent passes on as the exit code. This, along with setting the nscd service type to forking in its systemd configuration file, allows systemd to be certain that the nscd service is ready and is accepting connections.
426 lines
10 KiB
C
426 lines
10 KiB
C
/* SELinux access controls for nscd.
|
|
Copyright (C) 2004-2014 Free Software Foundation, Inc.
|
|
This file is part of the GNU C Library.
|
|
Contributed by Matthew Rickard <mjricka@epoch.ncsc.mil>, 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, see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include "config.h"
|
|
#include <error.h>
|
|
#include <errno.h>
|
|
#include <libintl.h>
|
|
#include <pthread.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
#include <sys/prctl.h>
|
|
#include <selinux/av_permissions.h>
|
|
#include <selinux/avc.h>
|
|
#include <selinux/flask.h>
|
|
#include <selinux/selinux.h>
|
|
#ifdef HAVE_LIBAUDIT
|
|
# include <libaudit.h>
|
|
#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 access_vector_t 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
|
|
#ifdef NSCD__GETNETGRP
|
|
[GETNETGRENT] = NSCD__GETNETGRP,
|
|
[INNETGR] = NSCD__GETNETGRP,
|
|
[GETFDNETGR] = NSCD__SHMEMNETGRP,
|
|
#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"));
|
|
do_exit (EXIT_FAILURE, errno, _("prctl(KEEPCAPS) failed"));
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
cap_t tmp_caps = cap_init ();
|
|
cap_t new_caps = NULL;
|
|
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"));
|
|
do_exit (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 (__glibc_unlikely (res != 0))
|
|
{
|
|
cap_free (new_caps);
|
|
dbg_log (_("Failed to drop capabilities"));
|
|
do_exit (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"));
|
|
do_exit (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"));
|
|
do_exit (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"));
|
|
do_exit (EXIT_FAILURE, 0, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
/* 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)
|
|
do_exit (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)
|
|
do_exit (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)
|
|
do_exit (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;
|
|
}
|
|
|
|
#ifndef NSCD__GETSERV
|
|
if (perms[req] == 0)
|
|
{
|
|
dbg_log (_("compile-time support for database policy missing"));
|
|
goto out;
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
|
|
#endif /* HAVE_SELINUX */
|