From f61449b0212170d1dd619738e243e8f409ea161c Mon Sep 17 00:00:00 2001 From: Robbie Harwood Date: Jun 07 2017 15:28:01 +0000 Subject: Support matching based on executable name Signed-off-by: Robbie Harwood Reviewed-by: Simo Sorce Resolves: #181 Merges: #187 --- diff --git a/man/gssproxy.conf.5.xml b/man/gssproxy.conf.5.xml index ed003c1..de846b4 100644 --- a/man/gssproxy.conf.5.xml +++ b/man/gssproxy.conf.5.xml @@ -349,6 +349,20 @@ + program (string) + + If specified, this service will only match when + the program being run is the specified string. + + Programs are assumed to be specified as + canonical paths (i.e., no relative paths, no + symlinks). Additionally, the '|' character is + reserved for future use and therefore forbidden. + + + + + trusted (boolean) Defines whether this service is considered trusted. Use with caution, this enables impersonation. Default: trusted = false diff --git a/src/gp_config.c b/src/gp_config.c index 31c5681..21bf80e 100644 --- a/src/gp_config.c +++ b/src/gp_config.c @@ -258,6 +258,16 @@ static int check_services(const struct gp_config *cfg) isock = GP_SOCKET_NAME; } + if (isvc->program) { + if (isvc->program[0] != '/') { + ret = 1; + GPERROR("Program paths must be absolute!\n"); + } else if (strchr(isvc->program, '|')) { + ret = 1; + GPERROR("The character '|' is invalid in program paths!\n"); + } + } + for (j = 0; j < i; j++) { jsvc = cfg->svcs[j]; jsock = jsvc->socket; @@ -266,18 +276,21 @@ static int check_services(const struct gp_config *cfg) } if (!gp_same(isock, jsock) || + !gp_same(isvc->program, jsvc->program) || !gp_selinux_ctx_equal(isvc->selinux_ctx, jsvc->selinux_ctx)) { continue; } if (jsvc->any_uid) { ret = 1; - GPERROR("%s sets allow_any_uid with the same socket and " - "selinux_context as %s!\n", jsvc->name, isvc->name); + GPERROR("%s sets allow_any_uid with the same socket, " + "selinux_context, and program as %s!\n", + jsvc->name, isvc->name); } else if (jsvc->euid == isvc->euid) { ret = 1; - GPERROR("socket, selinux_context, and euid for %s and %s " - "should not match!\n", isvc->name, jsvc->name); + GPERROR("socket, selinux_context, euid, and program for " + "%s and %s should not match!\n", + isvc->name, jsvc->name); } } } @@ -514,6 +527,15 @@ static int load_services(struct gp_config *cfg, struct gp_ini_context *ctx) ret = parse_flags(value, &cfg->svcs[n]->enforce_flags); if (ret) goto done; } + + ret = gp_config_get_string(ctx, secname, "program", &value); + if (ret == 0) { + cfg->svcs[n]->program = strdup(value); + if (!cfg->svcs[n]->program) { + ret = ENOMEM; + goto done; + } + } } safefree(secname); } diff --git a/src/gp_creds.c b/src/gp_creds.c index fdc6bdf..8d2e449 100644 --- a/src/gp_creds.c +++ b/src/gp_creds.c @@ -77,31 +77,28 @@ struct gp_service *gp_creds_match_conn(struct gssproxy_ctx *gpctx, { struct gp_creds *gcs; const char *socket; - int i; + const char *program; gcs = gp_conn_get_creds(conn); socket = gp_conn_get_socket(conn); + program = gp_conn_get_program(conn); - for (i = 0; i < gpctx->config->num_svcs; i++) { - if (gpctx->config->svcs[i]->any_uid || - gpctx->config->svcs[i]->euid == gcs->ucred.uid) { - if (gpctx->config->svcs[i]->socket) { - if (!gp_same(socket, gpctx->config->svcs[i]->socket)) { - continue; - } - } else { - if (!gp_same(socket, gpctx->config->socket_name)) { - continue; - } - } - if (!gp_conn_check_selinux(conn, - gpctx->config->svcs[i]->selinux_ctx)) { - continue; - } - return gpctx->config->svcs[i]; + for (int i = 0; i < gpctx->config->num_svcs; i++) { + struct gp_service *svc = gpctx->config->svcs[i]; + + if ((!svc->any_uid && svc->euid != gcs->ucred.uid) || + !gp_conn_check_selinux(conn, svc->selinux_ctx) || + (svc->program && !gp_same(program, svc->program)) || + (svc->socket && !gp_same(socket, svc->socket)) || + (!svc->socket && !gp_same(socket, gpctx->config->socket_name))) { + continue; } + + GPDEBUGN(2, "Connection matched service %s\n", svc->name); + return svc; } + GPDEBUGN(2, "No matching service found\n"); return NULL; } diff --git a/src/gp_proxy.h b/src/gp_proxy.h index 55ab83c..3e944ab 100644 --- a/src/gp_proxy.h +++ b/src/gp_proxy.h @@ -41,6 +41,7 @@ struct gp_service { gss_cred_usage_t cred_usage; uint32_t filter_flags; uint32_t enforce_flags; + char *program; uint32_t mechs; struct gp_cred_krb5 krb5; @@ -114,6 +115,7 @@ struct gp_creds *gp_conn_get_creds(struct gp_conn *conn); uid_t gp_conn_get_uid(struct gp_conn *conn); const char *gp_conn_get_socket(struct gp_conn *conn); int gp_conn_get_cid(struct gp_conn *conn); +const char *gp_conn_get_program(struct gp_conn *conn); bool gp_selinux_ctx_equal(SELINUX_CTX ctx1, SELINUX_CTX ctx2); bool gp_conn_check_selinux(struct gp_conn *conn, SELINUX_CTX ctx); diff --git a/src/gp_socket.c b/src/gp_socket.c index 5064e51..2198514 100644 --- a/src/gp_socket.c +++ b/src/gp_socket.c @@ -33,6 +33,7 @@ struct gp_conn { struct unix_sock_conn us; struct gp_creds creds; SELINUX_CTX selinux_ctx; + char *program; }; struct gp_buffer { @@ -108,6 +109,11 @@ int gp_conn_get_cid(struct gp_conn *conn) return conn->us.sd; } +const char *gp_conn_get_program(struct gp_conn *conn) +{ + return conn->program; +} + void gp_conn_free(struct gp_conn *conn) { if (!conn) return; @@ -115,6 +121,7 @@ void gp_conn_free(struct gp_conn *conn) if (conn->us.sd != -1) { close(conn->us.sd); } + free(conn->program); SELINUX_context_free(conn->selinux_ctx); free(conn); } @@ -273,6 +280,45 @@ static int get_peercred(int fd, struct gp_conn *conn) return 0; } +static char *get_program(pid_t pid) +{ + char procfile[21]; + char *program; + int ret, e; + struct stat sb; + + ret = snprintf(procfile, 20, "/proc/%u/exe", pid); + if (ret < 0) { + e = errno; + GPERROR("Internal error in snprintf: %d (%s)", e, strerror(e)); + return NULL; + } + procfile[ret] = '\0'; + + program = realpath(procfile, NULL); + if (program) { + return program; + } + + e = errno; + if (e != ENOENT) { + GPERROR("Unexpected failure in realpath: %d (%s)", e, strerror(e)); + return NULL; + } + + /* check if /proc is even around */ + procfile[ret - 4] = '\0'; + ret = stat(procfile, &sb); /* complains if we give it NULL */ + e = errno; + if (ret == -1 && e == ENOENT) { + /* kernel thread */ + return NULL; + } + + GPERROR("Problem with /proc; program name matching won't work: %d (%s)", + e, strerror(e)); + return NULL; +} static void gp_socket_read(verto_ctx *vctx, verto_ev *ev); @@ -563,7 +609,14 @@ void accept_sock_conn(verto_ctx *vctx, verto_ev *ev) goto done; } - GPDEBUG("Client connected (fd = %d)", fd); + conn->program = get_program(conn->creds.ucred.pid); + + GPDEBUG("Client "); + if (conn->program) { + GPDEBUG("(%s) ", conn->program); + } + GPDEBUG(" connected (fd = %d)", fd); + if (conn->creds.type & CRED_TYPE_UNIX) { GPDEBUG(" (pid = %d) (uid = %d) (gid = %d)", conn->creds.ucred.pid, diff --git a/tests/Makefile.am b/tests/Makefile.am index f6d6e56..bea6a51 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,4 +1,3 @@ - t_acquire_SOURCES = \ t_utils.c \ t_acquire.c @@ -64,6 +63,7 @@ EXTRA_DIST = \ t_impersonate.py \ t_interpose.py \ t_multi_key.py \ - t_reloading.py + t_reloading.py \ + $(NULL) all: $(check_PROGRAMS) diff --git a/tests/t_program.py b/tests/t_program.py new file mode 100644 index 0000000..1f71f07 --- /dev/null +++ b/tests/t_program.py @@ -0,0 +1,51 @@ +#!/usr/bin/python3 +# Copyright (C) 2017 - GSS-Proxy contributors; see COPYING for the license. + +from testlib import * + +from t_acquire import run as run_acquire_test + +import os + +GSSPROXY_PROGRAM = ''' +[gssproxy] + debug_level = 3 + +[service/t_acquire] + mechs = krb5 + cred_store = keytab:${GSSPROXY_KEYTAB} + cred_store = client_keytab:${GSSPROXY_CLIENT_KEYTAB} + trusted = yes + euid = ${UIDNUMBER} + allow_client_ccache_sync = yes + program = ${PROGDIR}/t_acquire +''' + +def run(testdir, env, conf): + prefix = conf["prefix"] + retval = 0 + + print("Testing positive program name matching...", file=sys.stderr) + sys.stderr.write(" ") + conf["prefix"] = prefix + "_1" + update_gssproxy_conf(testdir, conf["keysenv"], GSSPROXY_PROGRAM) + os.kill(conf["gpid"], signal.SIGHUP) + time.sleep(1) + retval |= run_acquire_test(testdir, env, conf) + + print("Testing negative program name matching...", file=sys.stderr) + sys.stderr.write(" ") + conf["prefix"] = prefix + "_2" + bad_progdir = GSSPROXY_PROGRAM.replace("${PROGDIR}", "//bad/path") + update_gssproxy_conf(testdir, conf["keysenv"], bad_progdir) + os.kill(conf["gpid"], signal.SIGHUP) + time.sleep(1) + retval |= run_acquire_test(testdir, env, conf, expected_failure=True) + + # be a good citizen and clean up after ourselves + update_gssproxy_conf(testdir, conf["keysenv"], GSSPROXY_CONF_TEMPLATE) + os.kill(conf["gpid"], signal.SIGHUP) + time.sleep(1) + + print_return(retval, "Program", False) + return retval diff --git a/tests/testlib.py b/tests/testlib.py index 08dfdd6..76dff2d 100755 --- a/tests/testlib.py +++ b/tests/testlib.py @@ -549,6 +549,7 @@ GSSPROXY_CONF_TEMPLATE = ''' # Contains a garbage service entry GSSPROXY_CONF_MINIMAL_TEMPLATE = ''' [gssproxy] + debug_level = 3 [service/dontuse] mechs = krb5 @@ -599,6 +600,7 @@ def update_gssproxy_conf(testdir, env, template): 'GSSPROXY_CLIENT_KEYTAB': ckeytab, 'UIDNUMBER': os.getuid(), 'SECOND_SOCKET': socket2, + 'PROGDIR': os.path.join(os.getcwd(), "tests"), 'TESTDIR': testdir} if 'client_name' in env: subs['GSSPROXY_CLIENT_PRINCIPAL'] = env['client_name']