diff options
-rw-r--r-- | lib/privs.c | 88 | ||||
-rw-r--r-- | lib/privs.h | 19 |
2 files changed, 94 insertions, 13 deletions
diff --git a/lib/privs.c b/lib/privs.c index 59f24afe4..a19707b1c 100644 --- a/lib/privs.c +++ b/lib/privs.c @@ -25,10 +25,25 @@ #include "privs.h" #include "memory.h" #include "lib_errors.h" +#include "lib/queue.h" +DEFINE_MTYPE_STATIC(LIB, PRIVS, "Privilege information") + +/* + * Different capabilities/privileges apis have different characteristics: some + * are process-wide, and some are per-thread. + */ #ifdef HAVE_CAPABILITIES +#ifdef HAVE_LCAPS +static const bool privs_per_process; /* = false */ +#elif defined(HAVE_SOLARIS_CAPABILITIES) +static const bool privs_per_process = true; +#endif +#else +static const bool privs_per_process = true; +#endif /* HAVE_CAPABILITIES */ -DEFINE_MTYPE_STATIC(LIB, PRIVS, "Privilege information") +#ifdef HAVE_CAPABILITIES /* sort out some generic internal types for: * @@ -698,25 +713,66 @@ static int getgrouplist(const char *user, gid_t group, gid_t *groups, } #endif /* HAVE_GETGROUPLIST */ +/* + * Helper function that locates a refcounting object to use: a process-wide + * object or a per-pthread object. + */ +static struct zebra_privs_refs_t *get_privs_refs(struct zebra_privs_t *privs) +{ + struct zebra_privs_refs_t *temp, *refs = NULL; + pthread_t tid; + + if (privs_per_process) + refs = &(privs->process_refs); + else { + /* Locate - or create - the object for the current pthread. */ + tid = pthread_self(); + + STAILQ_FOREACH(temp, &(privs->thread_refs), entry) { + if (pthread_equal(temp->tid, tid)) { + refs = temp; + break; + } + } + + /* Need to create a new refcounting object. */ + if (refs == NULL) { + refs = XCALLOC(MTYPE_PRIVS, + sizeof(struct zebra_privs_refs_t)); + refs->tid = tid; + STAILQ_INSERT_TAIL(&(privs->thread_refs), refs, entry); + } + } + + return refs; +} + struct zebra_privs_t *_zprivs_raise(struct zebra_privs_t *privs, const char *funcname) { int save_errno = errno; + struct zebra_privs_refs_t *refs; if (!privs) return NULL; - /* If we're already elevated, just return */ + /* + * Serialize 'raise' operations; particularly important for + * OSes where privs are process-wide. + */ pthread_mutex_lock(&(privs->mutex)); { - if (++(privs->refcount) == 1) { + /* Locate ref-counting object to use */ + refs = get_privs_refs(privs); + + if (++(refs->refcount) == 1) { errno = 0; if (privs->change(ZPRIVS_RAISE)) { zlog_err("%s: Failed to raise privileges (%s)", funcname, safe_strerror(errno)); } errno = save_errno; - privs->raised_in_funcname = funcname; + refs->raised_in_funcname = funcname; } } pthread_mutex_unlock(&(privs->mutex)); @@ -727,22 +783,27 @@ struct zebra_privs_t *_zprivs_raise(struct zebra_privs_t *privs, void _zprivs_lower(struct zebra_privs_t **privs) { int save_errno = errno; + struct zebra_privs_refs_t *refs; if (!*privs) return; - /* Don't lower privs if there's another caller */ + /* Serialize 'lower privs' operation - particularly important + * when OS privs are process-wide. + */ pthread_mutex_lock(&(*privs)->mutex); { - if (--((*privs)->refcount) == 0) { + refs = get_privs_refs(*privs); + + if (--(refs->refcount) == 0) { errno = 0; if ((*privs)->change(ZPRIVS_LOWER)) { zlog_err("%s: Failed to lower privileges (%s)", - (*privs)->raised_in_funcname, + refs->raised_in_funcname, safe_strerror(errno)); } errno = save_errno; - (*privs)->raised_in_funcname = NULL; + refs->raised_in_funcname = NULL; } } pthread_mutex_unlock(&(*privs)->mutex); @@ -761,7 +822,9 @@ void zprivs_preinit(struct zebra_privs_t *zprivs) } pthread_mutex_init(&(zprivs->mutex), NULL); - zprivs->refcount = 0; + zprivs->process_refs.refcount = 0; + zprivs->process_refs.raised_in_funcname = NULL; + STAILQ_INIT(&zprivs->thread_refs); if (zprivs->vty_group) { /* in a "NULL" setup, this is allowed to fail too, but still @@ -919,6 +982,8 @@ void zprivs_init(struct zebra_privs_t *zprivs) void zprivs_terminate(struct zebra_privs_t *zprivs) { + struct zebra_privs_refs_t *refs; + if (!zprivs) { fprintf(stderr, "%s: no privs struct given, terminating", __func__); @@ -941,6 +1006,11 @@ void zprivs_terminate(struct zebra_privs_t *zprivs) } #endif /* HAVE_LCAPS */ + while ((refs = STAILQ_FIRST(&(zprivs->thread_refs))) != NULL) { + STAILQ_REMOVE_HEAD(&(zprivs->thread_refs), entry); + XFREE(MTYPE_PRIVS, refs); + } + zprivs->change = zprivs_change_null; zprivs->current_state = zprivs_state_null; zprivs_null_state = ZPRIVS_LOWERED; diff --git a/lib/privs.h b/lib/privs.h index 01ddba462..2b0b44b3f 100644 --- a/lib/privs.h +++ b/lib/privs.h @@ -24,6 +24,7 @@ #define _ZEBRA_PRIVS_H #include <pthread.h> +#include "lib/queue.h" #ifdef __cplusplus extern "C" { @@ -56,6 +57,13 @@ typedef enum { ZPRIVS_LOWER, } zebra_privs_ops_t; +struct zebra_privs_refs_t { + STAILQ_ENTRY(zebra_privs_refs_t) entry; + pthread_t tid; + uint32_t refcount; + const char *raised_in_funcname; +}; + struct zebra_privs_t { zebra_capabilities_t *caps_p; /* caps required for operation */ zebra_capabilities_t *caps_i; /* caps to allow inheritance of */ @@ -63,11 +71,15 @@ struct zebra_privs_t { int cap_num_i; /* Mutex and counter used to avoid race conditions in multi-threaded - * processes. The privs elevation is process-wide, so we need to - * avoid changing the privilege status across threads. + * processes. If privs status is process-wide, we need to + * control changes to the privilege status among threads. + * If privs changes are per-thread, we need to be able to + * manage that too. */ pthread_mutex_t mutex; - uint32_t refcount; + struct zebra_privs_refs_t process_refs; + + STAILQ_HEAD(thread_refs_q, zebra_privs_refs_t) thread_refs; const char *user; /* user and group to run as */ const char *group; @@ -76,7 +88,6 @@ struct zebra_privs_t { int (*change)(zebra_privs_ops_t); /* change privileges, 0 on success */ zebra_privs_current_t (*current_state)( void); /* current privilege state */ - const char *raised_in_funcname; }; struct zprivs_ids_t { |