/
githubmirror
/
gssproxy
Обзор
Документация
Войти
/
githubmirror
/
gssproxy
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/client/gpm_common.c
850 строк
21 KB
Simo Sorce
Support clients running in AT_SECURE context
08 июл 2026, 16:24
08 июл 2026, 16:24
c92e174
Код
Авторство
О чём код?
/* Copyright (C) 2011,2017 the GSS-PROXY contributors, see COPYING for license */ #include "gssapi_gpm.h" #include <sys/types.h> #include <sys/un.h> #include <unistd.h> #include <stdlib.h> #include <time.h> #include <pthread.h> #include <sys/epoll.h> #include <fcntl.h> #include <sys/auxv.h> #include <sys/random.h> #include <sys/timerfd.h> #define FRAGMENT_BIT (1 << 31) #define RESPONSE_TIMEOUT 15 #define SAFETY_TIMEOUT RESPONSE_TIMEOUT * 10 * 1000 #define MAX_TIMEOUT_RETRY 3 struct gpm_ctx { pthread_mutex_t lock; int fd; /* these are only meaningful if fd != -1 */ pid_t pid; uid_t uid; gid_t gid; int next_xid; int epollfd; int timerfd; }; /* a single global struct is not particularly efficient, * but will do for now */ struct gpm_ctx gpm_global_ctx; pthread_once_t gpm_init_once_control = PTHREAD_ONCE_INIT; static void gpm_init_once(void) { pthread_mutexattr_t attr; char *buf = (char *)&gpm_global_ctx.next_xid; size_t len = sizeof(gpm_global_ctx.next_xid); size_t ret = 0; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&gpm_global_ctx.lock, &attr); gpm_global_ctx.fd = -1; gpm_global_ctx.epollfd = -1; gpm_global_ctx.timerfd = -1; while(ret < len) { ret += getrandom(buf + ret, len - ret, 0); } pthread_mutexattr_destroy(&attr); gpm_display_status_init_once(); } static int get_program_name(char *name, size_t size) { ssize_t ret; char *p; ret = readlink("/proc/self/exe", name, size - 1); if (ret == -1) { return errno; } name[ret] = '\0'; p = strrchr(name, '/'); if (p) { p++; memmove(name, p, ret + 1 - (p - name)); } return 0; } static int get_pipe_name(char *name) { const char *socket; unsigned long auxval; int ret = 1; /* check if this is a "secure" client */ auxval = getauxval(AT_SECURE); if (auxval == 0) { /* default case */ socket = gp_getenv("GSSPROXY_SOCKET"); if (!socket) { socket = GP_SOCKET_NAME; } ret = snprintf(name, PATH_MAX, "%s", socket); } else { char proc[PATH_MAX]; ret = get_program_name(proc, sizeof(proc)); if (ret) { return ret; } ret = snprintf(name, PATH_MAX, "%s.%s", GP_SOCKET_NAME, proc); } if (ret < 0 || ret >= PATH_MAX) { return ENAMETOOLONG; } return 0; } static int gpm_open_socket(struct gpm_ctx *gpmctx) { struct sockaddr_un addr = {0}; char name[PATH_MAX]; int ret; int fd = -1; ret = get_pipe_name(name); if (ret) { return ret; } addr.sun_family = AF_UNIX; strncpy(addr.sun_path, name, sizeof(addr.sun_path)-1); addr.sun_path[sizeof(addr.sun_path)-1] = '\0'; fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); if (fd == -1) { ret = errno; goto done; } ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); if (ret == -1) { ret = errno; } done: if (ret) { if (fd != -1) { close(fd); fd = -1; } } gpmctx->fd = fd; gpmctx->pid = getpid(); gpmctx->uid = geteuid(); gpmctx->gid = getegid(); return ret; } static void gpm_close_socket(struct gpm_ctx *gpmctx) { /* In theory, we should retry close() on EINTR, but the fd will be invalid * after close() has been called, so closing again may cause a race with * another thread that just happend to open an unrelated file descriptor. * So until POSIX finally amends language around close() and at least the * Linux kernel changes its behavior, it is better to risk a leak than * closing an unrelated file descriptor. */ close(gpmctx->fd); gpmctx->fd = -1; } static void gpm_timer_close(struct gpm_ctx *gpmctx) { if (gpmctx->timerfd < 0) { return; } close(gpmctx->timerfd); gpmctx->timerfd = -1; } static int gpm_timer_setup(struct gpm_ctx *gpmctx, int timeout_seconds) { int ret; struct itimerspec its; if (gpmctx->timerfd >= 0) { gpm_timer_close(gpmctx); } gpmctx->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (gpmctx->timerfd < 0) { return errno; } its.it_interval.tv_sec = timeout_seconds; its.it_interval.tv_nsec = 0; its.it_value.tv_sec = timeout_seconds; its.it_value.tv_nsec = 0; ret = timerfd_settime(gpmctx->timerfd, 0, &its, NULL); if (ret) { ret = errno; gpm_timer_close(gpmctx); return ret; } return 0; } static void gpm_epoll_close(struct gpm_ctx *gpmctx) { if (gpmctx->epollfd < 0) { return; } close(gpmctx->epollfd); gpmctx->epollfd = -1; } static int gpm_epoll_setup(struct gpm_ctx *gpmctx) { struct epoll_event ev; int ret; memset(&ev, 0, sizeof(ev)); if (gpmctx->epollfd >= 0) { gpm_epoll_close(gpmctx); } gpmctx->epollfd = epoll_create1(EPOLL_CLOEXEC); if (gpmctx->epollfd == -1) { return errno; } /* Add timer */ ev.events = EPOLLIN; ev.data.fd = gpmctx->timerfd; ret = epoll_ctl(gpmctx->epollfd, EPOLL_CTL_ADD, gpmctx->timerfd, &ev); if (ret == -1) { ret = errno; gpm_epoll_close(gpmctx); return ret; } return ret; } static int gpm_release_sock(struct gpm_ctx *gpmctx) { gpm_epoll_close(gpmctx); gpm_timer_close(gpmctx); return pthread_mutex_unlock(&gpmctx->lock); } static int gpm_grab_sock(struct gpm_ctx *gpmctx) { int ret; pid_t p; uid_t u; gid_t g; ret = pthread_mutex_lock(&gpmctx->lock); if (ret) { return ret; } /* Detect fork / setresuid and friends */ p = getpid(); u = geteuid(); g = getegid(); if (gpmctx->fd != -1 && (p != gpmctx->pid || u != gpmctx->uid || g != gpmctx->gid)) { gpm_close_socket(gpmctx); } if (gpmctx->fd == -1) { ret = gpm_open_socket(gpmctx); if (ret) { goto done; } } /* setup timer */ ret = gpm_timer_setup(gpmctx, RESPONSE_TIMEOUT); if (ret) { goto done; } /* create epoll fd as well */ ret = gpm_epoll_setup(gpmctx); done: if (ret) { gpm_release_sock(gpmctx); } return ret; } static int gpm_epoll_wait(struct gpm_ctx *gpmctx, uint32_t event_flags) { int ret; int epoll_ret; struct epoll_event ev; struct epoll_event events[2]; uint64_t timer_read; memset(&ev, 0, sizeof(ev)); memset(&events[0], 0, sizeof(events[0])); memset(&events[1], 0, sizeof(events[1])); if (gpmctx->epollfd < 0) { ret = gpm_epoll_setup(gpmctx); if (ret) return ret; } ev.events = event_flags; ev.data.fd = gpmctx->fd; epoll_ret = epoll_ctl(gpmctx->epollfd, EPOLL_CTL_ADD, gpmctx->fd, &ev); if (epoll_ret == -1) { ret = errno; gpm_epoll_close(gpmctx); return ret; } do { epoll_ret = epoll_wait(gpmctx->epollfd, events, 2, SAFETY_TIMEOUT); } while (epoll_ret < 0 && errno == EINTR); if (epoll_ret < 0) { /* Error while waiting that isn't EINTR */ ret = errno; gpm_epoll_close(gpmctx); } else if (epoll_ret == 0) { ret = ETIMEDOUT; gpm_epoll_close(gpmctx); } else if (epoll_ret == 1 && events[0].data.fd == gpmctx->timerfd) { /* Got an event which is only our timer */ if ((events[0].events & EPOLLIN) == 0) { /* We got an event which was not EPOLLIN; assume this is an error, * and exit with EBADF: epoll_wait said timerfd had an event, * but that event is not an EPOLIN event. */ ret = EBADF; } else { ret = read(gpmctx->timerfd, &timer_read, sizeof(uint64_t)); if (ret == -1 && errno != EAGAIN && errno != EWOULDBLOCK) { /* In the case when reading from the timer failed, don't hide the * timer error behind ETIMEDOUT such that it isn't retried */ ret = errno; } else { /* If ret == 0, then we definitely timed out. Else, if ret == -1 * and errno == EAGAIN or errno == EWOULDBLOCK, we're in a weird * edge case where epoll thinks the timer can be read, but it * is blocking more; treat it like a TIMEOUT and retry, as * nothing around us would handle EAGAIN from timer and retry * it. */ ret = ETIMEDOUT; } } gpm_epoll_close(gpmctx); } else { /* If ret == 2, then we ignore the timerfd; that way if the next * operation cannot be performed immediately, we timeout and retry. * Always check the returned event of the socket fd. */ int fd_index = 0; if (epoll_ret == 2 && events[fd_index].data.fd != gpmctx->fd) { fd_index = 1; } if ((events[fd_index].events & event_flags) == 0) { /* We cannot call EPOLLIN/EPOLLOUT at this time; assume that this * is a fatal error; return with EBADFD to distinguish from * EBADF in timer_fd case. */ ret = EBADFD; gpm_epoll_close(gpmctx); } else { /* We definintely got a EPOLLIN/EPOLLOUT event; return success. */ ret = 0; } } epoll_ret = epoll_ctl(gpmctx->epollfd, EPOLL_CTL_DEL, gpmctx->fd, NULL); if (epoll_ret == -1) { /* If we previously had an error, expose that error instead of * clobbering it with errno; else if no error, then assume it is * better to notify of the error deleting the event than it is * to continue. */ if (ret == 0) ret = errno; gpm_epoll_close(gpmctx); } return ret; } static int gpm_retry_socket(struct gpm_ctx *gpmctx) { gpm_epoll_close(gpmctx); gpm_close_socket(gpmctx); return gpm_open_socket(gpmctx); } /* must be called after the lock has been grabbed */ static int gpm_send_buffer(struct gpm_ctx *gpmctx, char *buffer, uint32_t length) { uint32_t size; ssize_t wn; size_t pos; bool retry; int ret; if (length > MAX_RPC_SIZE) { return EINVAL; } size = length | FRAGMENT_BIT; size = htonl(size); retry = false; do { do { ret = gpm_epoll_wait(gpmctx, EPOLLOUT); if (ret != 0) { goto done; } ret = 0; wn = send(gpmctx->fd, &size, sizeof(uint32_t), MSG_NOSIGNAL); if (wn == -1) { ret = errno; } } while (ret == EINTR); if (wn != 4) { /* reopen and retry once */ if (retry == false) { ret = gpm_retry_socket(gpmctx); if (ret == 0) { retry = true; continue; } } else { ret = EIO; } goto done; } retry = false; } while (retry); pos = 0; while (length > pos) { ret = gpm_epoll_wait(gpmctx, EPOLLOUT); if (ret) { goto done; } wn = send(gpmctx->fd, buffer + pos, length - pos, MSG_NOSIGNAL); if (wn == -1) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) { continue; } ret = errno; goto done; } pos += wn; } ret = 0; done: /* we only need to return as gpm_retry_socket closes the socket */ return ret; } /* must be called after the lock has been grabbed */ static int gpm_recv_buffer(struct gpm_ctx *gpmctx, char **buffer, uint32_t *length) { uint32_t size; ssize_t rn; size_t pos; int ret; do { ret = gpm_epoll_wait(gpmctx, EPOLLIN); if (ret) { goto done; } ret = 0; rn = read(gpmctx->fd, &size, sizeof(uint32_t)); if (rn == -1) { ret = errno; } } while (ret == EINTR); if (rn != 4) { ret = EIO; goto done; } *length = ntohl(size); *length &= ~FRAGMENT_BIT; if (*length > MAX_RPC_SIZE) { ret = EMSGSIZE; goto done; } *buffer = malloc(*length); if (*buffer == NULL) { ret = ENOMEM; goto done; } pos = 0; while (*length > pos) { ret = gpm_epoll_wait(gpmctx, EPOLLIN); if (ret) { goto done; } rn = read(gpmctx->fd, *buffer + pos, *length - pos); if (rn == -1) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) { continue; } ret = errno; goto done; } if (rn == 0) { ret = EIO; goto done; } pos += rn; } ret = 0; done: if (ret) { /* on errors, free the buffer to prevent calling * xdr_destroy(&xdr_reply_ctx); */ free(*buffer); *buffer = NULL; } return ret; } /* must be called after the lock has been grabbed */ static uint32_t gpm_next_xid(struct gpm_ctx *gpmctx) { uint32_t xid; if (gpmctx->next_xid < 0) { gpmctx->next_xid = 1; xid = 0; } else { xid = gpmctx->next_xid++; } return xid; } static struct gpm_ctx *gpm_get_ctx(void) { pthread_once(&gpm_init_once_control, gpm_init_once); return &gpm_global_ctx; } static int gpm_send_recv_loop(struct gpm_ctx *gpmctx, char *send_buffer, uint32_t send_length, char** recv_buffer, uint32_t *recv_length) { int ret; int retry_count; for (retry_count = 0; retry_count < MAX_TIMEOUT_RETRY; retry_count++) { /* send to proxy */ ret = gpm_send_buffer(gpmctx, send_buffer, send_length); if (ret == 0) { /* No error, continue to recv */ } else if (ret == ETIMEDOUT) { /* Close and reopen socket before trying again */ ret = gpm_retry_socket(gpmctx); if (ret != 0) return ret; ret = ETIMEDOUT; /* RETRY entire send */ continue; } else { /* Other error */ return ret; } /* receive answer */ ret = gpm_recv_buffer(gpmctx, recv_buffer, recv_length); if (ret == 0) { /* No error */ break; } else if (ret == ETIMEDOUT) { /* Close and reopen socket before trying again */ ret = gpm_retry_socket(gpmctx); if (ret != 0) return ret; ret = ETIMEDOUT; } else { /* Other error */ return ret; } } return ret; } OM_uint32 gpm_release_buffer(OM_uint32 *minor_status, gss_buffer_t buffer) { *minor_status = 0; if (buffer != GSS_C_NO_BUFFER) { if (buffer->value) { free(buffer->value); } buffer->length = 0; buffer->value = NULL; } return GSS_S_COMPLETE; } struct gpm_rpc_fn_set { xdrproc_t arg_fn; xdrproc_t res_fn; } gpm_xdr_set[] = { { /* NULLPROC */ (xdrproc_t)xdr_void, (xdrproc_t)xdr_void, }, { /* GSSX_INDICATE_MECHS */ (xdrproc_t)xdr_gssx_arg_indicate_mechs, (xdrproc_t)xdr_gssx_res_indicate_mechs, }, { /* GSSX_GET_CALL_CONTEXT */ (xdrproc_t)xdr_gssx_arg_get_call_context, (xdrproc_t)xdr_gssx_res_get_call_context, }, { /* GSSX_IMPORT_AND_CANON_NAME */ (xdrproc_t)xdr_gssx_arg_import_and_canon_name, (xdrproc_t)xdr_gssx_res_import_and_canon_name, }, { /* GSSX_EXPORT_CRED */ (xdrproc_t)xdr_gssx_arg_export_cred, (xdrproc_t)xdr_gssx_res_export_cred, }, { /* GSSX_IMPORT_CRED */ (xdrproc_t)xdr_gssx_arg_import_cred, (xdrproc_t)xdr_gssx_res_import_cred, }, { /* GSSX_ACQUIRE_CRED */ (xdrproc_t)xdr_gssx_arg_acquire_cred, (xdrproc_t)xdr_gssx_res_acquire_cred, }, { /* GSSX_STORE_CRED */ (xdrproc_t)xdr_gssx_arg_store_cred, (xdrproc_t)xdr_gssx_res_store_cred, }, { /* GSSX_INIT_SEC_CONTEXT */ (xdrproc_t)xdr_gssx_arg_init_sec_context, (xdrproc_t)xdr_gssx_res_init_sec_context, }, { /* GSSX_ACCEPT_SEC_CONTEXT */ (xdrproc_t)xdr_gssx_arg_accept_sec_context, (xdrproc_t)xdr_gssx_res_accept_sec_context, }, { /* GSSX_RELEASE_HANDLE */ (xdrproc_t)xdr_gssx_arg_release_handle, (xdrproc_t)xdr_gssx_res_release_handle, }, { /* GSSX_GET_MIC */ (xdrproc_t)xdr_gssx_arg_get_mic, (xdrproc_t)xdr_gssx_res_get_mic, }, { /* GSSX_VERIFY */ (xdrproc_t)xdr_gssx_arg_verify_mic, (xdrproc_t)xdr_gssx_res_verify_mic, }, { /* GSSX_WRAP */ (xdrproc_t)xdr_gssx_arg_wrap, (xdrproc_t)xdr_gssx_res_wrap, }, { /* GSSX_UNWRAP */ (xdrproc_t)xdr_gssx_arg_unwrap, (xdrproc_t)xdr_gssx_res_unwrap, }, { /* GSSX_WRAP_SIZE_LIMIT */ (xdrproc_t)xdr_gssx_arg_wrap_size_limit, (xdrproc_t)xdr_gssx_res_wrap_size_limit, } }; int gpm_sock_check(void) { struct gpm_ctx *gpmctx; int ret; gpmctx = gpm_get_ctx(); if (!gpmctx) { return EFAULT; } ret = gpm_grab_sock(gpmctx); if (ret == 0) { gpm_release_sock(gpmctx); } return ret; } int gpm_make_call(int proc, union gp_rpc_arg *arg, union gp_rpc_res *res) { struct gpm_ctx *gpmctx; gp_rpc_msg msg; XDR xdr_call_ctx = {0}; XDR xdr_reply_ctx = {0}; xdrfn *arg_fn; xdrfn *res_fn; char *send_buffer = NULL; char *recv_buffer = NULL; uint32_t send_length; uint32_t recv_length; uint32_t xid; bool xdrok; bool sockgrab = false; int ret; send_buffer = malloc(MAX_RPC_SIZE); if (send_buffer == NULL) return ENOMEM; xdrmem_create(&xdr_call_ctx, send_buffer, MAX_RPC_SIZE, XDR_ENCODE); memset(&msg, 0, sizeof(gp_rpc_msg)); msg.header.type = GP_RPC_CALL; msg.header.gp_rpc_msg_union_u.chdr.rpcvers = 2; msg.header.gp_rpc_msg_union_u.chdr.prog = GSSPROXY; msg.header.gp_rpc_msg_union_u.chdr.vers = GSSPROXYVERS; msg.header.gp_rpc_msg_union_u.chdr.proc = proc; msg.header.gp_rpc_msg_union_u.chdr.cred.flavor = GP_RPC_AUTH_NONE; msg.header.gp_rpc_msg_union_u.chdr.cred.body.body_len = 0; msg.header.gp_rpc_msg_union_u.chdr.cred.body.body_val = NULL; msg.header.gp_rpc_msg_union_u.chdr.verf.flavor = GP_RPC_AUTH_NONE; msg.header.gp_rpc_msg_union_u.chdr.verf.body.body_len = 0; msg.header.gp_rpc_msg_union_u.chdr.verf.body.body_val = NULL; gpmctx = gpm_get_ctx(); if (!gpmctx) { return EINVAL; } /* grab the lock for the whole conversation */ ret = gpm_grab_sock(gpmctx); if (ret) { goto done; } sockgrab = true; msg.xid = xid = gpm_next_xid(gpmctx); /* encode header */ xdrok = xdr_gp_rpc_msg(&xdr_call_ctx, &msg); if (!xdrok) { ret = EINVAL; goto done; } /* encode data */ arg_fn = gpm_xdr_set[proc].arg_fn; xdrok = arg_fn(&xdr_call_ctx, arg); if (!xdrok) { ret = EINVAL; goto done; } /* set send_length */ send_length = xdr_getpos(&xdr_call_ctx); /* Send request, receive response with timeout */ ret = gpm_send_recv_loop(gpmctx, send_buffer, send_length, &recv_buffer, &recv_length); if (ret) goto done; /* release the lock */ gpm_release_sock(gpmctx); sockgrab = false; /* Create the reply context */ xdrmem_create(&xdr_reply_ctx, recv_buffer, recv_length, XDR_DECODE); /* decode header */ memset(&msg, 0, sizeof(gp_rpc_msg)); xdrok = xdr_gp_rpc_msg(&xdr_reply_ctx, &msg); if (!xdrok) { ret = EINVAL; goto done; } if (msg.xid != xid || msg.header.type != GP_RPC_REPLY || msg.header.gp_rpc_msg_union_u.rhdr.status != GP_RPC_MSG_ACCEPTED || msg.header.gp_rpc_msg_union_u.rhdr.gp_rpc_reply_header_u.accepted.reply_data.status != GP_RPC_SUCCESS) { ret = EINVAL; goto done; } /* decode answer */ res_fn = gpm_xdr_set[proc].res_fn; xdrok = res_fn(&xdr_reply_ctx, res); if (!xdrok) { ret = EINVAL; } done: if (sockgrab) { gpm_release_sock(gpmctx); } xdr_free((xdrproc_t)xdr_gp_rpc_msg, (char *)&msg); xdr_destroy(&xdr_call_ctx); if (recv_buffer != NULL) xdr_destroy(&xdr_reply_ctx); free(send_buffer); free(recv_buffer); return ret; } void gpm_free_xdrs(int proc, union gp_rpc_arg *arg, union gp_rpc_res *res) { xdr_free(gpm_xdr_set[proc].arg_fn, (char *)arg); xdr_free(gpm_xdr_set[proc].res_fn, (char *)res); }