diff --git a/config.h.in b/config.h.in index 1711a49d1..c22bdd7f5 100644 --- a/config.h.in +++ b/config.h.in @@ -842,9 +842,6 @@ /* Define to 1 if the system has the type `struct in6_addr'. */ #undef HAVE_STRUCT_IN6_ADDR -/* Define to 1 if `msg_control' is a member of `struct msghdr'. */ -#undef HAVE_STRUCT_MSGHDR_MSG_CONTROL - /* Define to 1 if `pr_ttydev' is a member of `struct psinfo'. */ #undef HAVE_STRUCT_PSINFO_PR_TTYDEV diff --git a/configure b/configure index 5f8f1046d..49b443824 100755 --- a/configure +++ b/configure @@ -23254,19 +23254,6 @@ then : printf "%s\n" "#define HAVE_STRUCT_DIRENT_D_NAMLEN 1" >>confdefs.h -fi - -ac_fn_c_check_member "$LINENO" "struct msghdr" "msg_control" "ac_cv_member_struct_msghdr_msg_control" " -$ac_includes_default -#include - -" -if test "x$ac_cv_member_struct_msghdr_msg_control" = xyes -then : - -printf "%s\n" "#define HAVE_STRUCT_MSGHDR_MSG_CONTROL 1" >>confdefs.h - - fi openssl_missing=no diff --git a/configure.ac b/configure.ac index e66d08008..5d9801423 100644 --- a/configure.ac +++ b/configure.ac @@ -2925,13 +2925,6 @@ AC_INCLUDES_DEFAULT #include <$ac_header_dirent> ]) dnl -dnl Check for POSIX sendmsg() ancillary data support. -dnl -AC_CHECK_MEMBERS(struct msghdr.msg_control, [], [], [ -AC_INCLUDES_DEFAULT -#include -]) -dnl dnl Check for functions only present in OpenSSL 1.1 and above dnl openssl_missing=no diff --git a/include/intercept.pb-c.h b/include/intercept.pb-c.h index c2913f411..2748aa709 100644 --- a/include/intercept.pb-c.h +++ b/include/intercept.pb-c.h @@ -15,12 +15,14 @@ PROTOBUF_C__BEGIN_DECLS #endif -typedef struct _InterceptMessage InterceptMessage; +typedef struct _InterceptRequest InterceptRequest; +typedef struct _ClientHello ClientHello; +typedef struct _HelloResponse HelloResponse; typedef struct _PolicyCheckRequest PolicyCheckRequest; typedef struct _PolicyAcceptMessage PolicyAcceptMessage; typedef struct _PolicyRejectMessage PolicyRejectMessage; typedef struct _PolicyErrorMessage PolicyErrorMessage; -typedef struct _PolicyCheckResult PolicyCheckResult; +typedef struct _InterceptResponse InterceptResponse; /* --- enums --- */ @@ -29,30 +31,62 @@ typedef struct _PolicyCheckResult PolicyCheckResult; /* --- messages --- */ typedef enum { - INTERCEPT_MESSAGE__TYPE__NOT_SET = 0, - INTERCEPT_MESSAGE__TYPE_POLICY_CHECK_REQ = 1 - PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(INTERCEPT_MESSAGE__TYPE) -} InterceptMessage__TypeCase; + INTERCEPT_REQUEST__TYPE__NOT_SET = 0, + INTERCEPT_REQUEST__TYPE_POLICY_CHECK_REQ = 1, + INTERCEPT_REQUEST__TYPE_HELLO = 2 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(INTERCEPT_REQUEST__TYPE) +} InterceptRequest__TypeCase; /* * Intercept message from sudo_intercept.so. Messages on the * wire are prefixed with a 32-bit size in network byte order. */ -struct _InterceptMessage +struct _InterceptRequest { ProtobufCMessage base; - InterceptMessage__TypeCase type_case; + InterceptRequest__TypeCase type_case; union { PolicyCheckRequest *policy_check_req; + ClientHello *hello; } u; }; -#define INTERCEPT_MESSAGE__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&intercept_message__descriptor) \ - , INTERCEPT_MESSAGE__TYPE__NOT_SET, {0} } +#define INTERCEPT_REQUEST__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&intercept_request__descriptor) \ + , INTERCEPT_REQUEST__TYPE__NOT_SET, {0} } + + +/* + * Hello message from sudo_intercept.so to main sudo process. + * Sudo sends back the secret and localhost port number. + */ +struct _ClientHello +{ + ProtobufCMessage base; + int32_t pid; +}; +#define CLIENT_HELLO__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&client_hello__descriptor) \ + , 0 } + + +/* + * Sudo response to a ClientHello from sudo_intercept.so. + * The client uses the port number and secret to connect back to sudo. + */ +struct _HelloResponse +{ + ProtobufCMessage base; + uint64_t secret; + int32_t portno; +}; +#define HELLO_RESPONSE__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&hello_response__descriptor) \ + , 0, 0 } /* * Policy check request from sudo_intercept.so. + * Must include the correct secret value. * Note that the plugin API only currently supports passing * the new environment in to the open() function. */ @@ -64,10 +98,12 @@ struct _PolicyCheckRequest char **argv; size_t n_envp; char **envp; + int32_t intercept_fd; + uint64_t secret; }; #define POLICY_CHECK_REQUEST__INIT \ { PROTOBUF_C_MESSAGE_INIT (&policy_check_request__descriptor) \ - , (char *)protobuf_c_empty_string, 0,NULL, 0,NULL } + , (char *)protobuf_c_empty_string, 0,NULL, 0,NULL, 0, 0 } struct _PolicyAcceptMessage @@ -105,50 +141,89 @@ struct _PolicyErrorMessage typedef enum { - POLICY_CHECK_RESULT__TYPE__NOT_SET = 0, - POLICY_CHECK_RESULT__TYPE_ACCEPT_MSG = 1, - POLICY_CHECK_RESULT__TYPE_REJECT_MSG = 2, - POLICY_CHECK_RESULT__TYPE_ERROR_MSG = 3 - PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(POLICY_CHECK_RESULT__TYPE) -} PolicyCheckResult__TypeCase; + INTERCEPT_RESPONSE__TYPE__NOT_SET = 0, + INTERCEPT_RESPONSE__TYPE_HELLO_RESP = 1, + INTERCEPT_RESPONSE__TYPE_ACCEPT_MSG = 2, + INTERCEPT_RESPONSE__TYPE_REJECT_MSG = 3, + INTERCEPT_RESPONSE__TYPE_ERROR_MSG = 4 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(INTERCEPT_RESPONSE__TYPE) +} InterceptResponse__TypeCase; /* - * Policy check result sent back to sudo_intercept.so. + * Response sent back to sudo_intercept.so. */ -struct _PolicyCheckResult +struct _InterceptResponse { ProtobufCMessage base; - uint64_t secret; - PolicyCheckResult__TypeCase type_case; + InterceptResponse__TypeCase type_case; union { + HelloResponse *hello_resp; PolicyAcceptMessage *accept_msg; PolicyRejectMessage *reject_msg; PolicyErrorMessage *error_msg; } u; }; -#define POLICY_CHECK_RESULT__INIT \ - { PROTOBUF_C_MESSAGE_INIT (&policy_check_result__descriptor) \ - , 0, POLICY_CHECK_RESULT__TYPE__NOT_SET, {0} } +#define INTERCEPT_RESPONSE__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&intercept_response__descriptor) \ + , INTERCEPT_RESPONSE__TYPE__NOT_SET, {0} } -/* InterceptMessage methods */ -void intercept_message__init - (InterceptMessage *message); -size_t intercept_message__get_packed_size - (const InterceptMessage *message); -size_t intercept_message__pack - (const InterceptMessage *message, +/* InterceptRequest methods */ +void intercept_request__init + (InterceptRequest *message); +size_t intercept_request__get_packed_size + (const InterceptRequest *message); +size_t intercept_request__pack + (const InterceptRequest *message, uint8_t *out); -size_t intercept_message__pack_to_buffer - (const InterceptMessage *message, +size_t intercept_request__pack_to_buffer + (const InterceptRequest *message, ProtobufCBuffer *buffer); -InterceptMessage * - intercept_message__unpack +InterceptRequest * + intercept_request__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); -void intercept_message__free_unpacked - (InterceptMessage *message, +void intercept_request__free_unpacked + (InterceptRequest *message, + ProtobufCAllocator *allocator); +/* ClientHello methods */ +void client_hello__init + (ClientHello *message); +size_t client_hello__get_packed_size + (const ClientHello *message); +size_t client_hello__pack + (const ClientHello *message, + uint8_t *out); +size_t client_hello__pack_to_buffer + (const ClientHello *message, + ProtobufCBuffer *buffer); +ClientHello * + client_hello__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void client_hello__free_unpacked + (ClientHello *message, + ProtobufCAllocator *allocator); +/* HelloResponse methods */ +void hello_response__init + (HelloResponse *message); +size_t hello_response__get_packed_size + (const HelloResponse *message); +size_t hello_response__pack + (const HelloResponse *message, + uint8_t *out); +size_t hello_response__pack_to_buffer + (const HelloResponse *message, + ProtobufCBuffer *buffer); +HelloResponse * + hello_response__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void hello_response__free_unpacked + (HelloResponse *message, ProtobufCAllocator *allocator); /* PolicyCheckRequest methods */ void policy_check_request__init @@ -226,29 +301,35 @@ PolicyErrorMessage * void policy_error_message__free_unpacked (PolicyErrorMessage *message, ProtobufCAllocator *allocator); -/* PolicyCheckResult methods */ -void policy_check_result__init - (PolicyCheckResult *message); -size_t policy_check_result__get_packed_size - (const PolicyCheckResult *message); -size_t policy_check_result__pack - (const PolicyCheckResult *message, +/* InterceptResponse methods */ +void intercept_response__init + (InterceptResponse *message); +size_t intercept_response__get_packed_size + (const InterceptResponse *message); +size_t intercept_response__pack + (const InterceptResponse *message, uint8_t *out); -size_t policy_check_result__pack_to_buffer - (const PolicyCheckResult *message, +size_t intercept_response__pack_to_buffer + (const InterceptResponse *message, ProtobufCBuffer *buffer); -PolicyCheckResult * - policy_check_result__unpack +InterceptResponse * + intercept_response__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); -void policy_check_result__free_unpacked - (PolicyCheckResult *message, +void intercept_response__free_unpacked + (InterceptResponse *message, ProtobufCAllocator *allocator); /* --- per-message closures --- */ -typedef void (*InterceptMessage_Closure) - (const InterceptMessage *message, +typedef void (*InterceptRequest_Closure) + (const InterceptRequest *message, + void *closure_data); +typedef void (*ClientHello_Closure) + (const ClientHello *message, + void *closure_data); +typedef void (*HelloResponse_Closure) + (const HelloResponse *message, void *closure_data); typedef void (*PolicyCheckRequest_Closure) (const PolicyCheckRequest *message, @@ -262,8 +343,8 @@ typedef void (*PolicyRejectMessage_Closure) typedef void (*PolicyErrorMessage_Closure) (const PolicyErrorMessage *message, void *closure_data); -typedef void (*PolicyCheckResult_Closure) - (const PolicyCheckResult *message, +typedef void (*InterceptResponse_Closure) + (const InterceptResponse *message, void *closure_data); /* --- services --- */ @@ -271,12 +352,14 @@ typedef void (*PolicyCheckResult_Closure) /* --- descriptors --- */ -extern const ProtobufCMessageDescriptor intercept_message__descriptor; +extern const ProtobufCMessageDescriptor intercept_request__descriptor; +extern const ProtobufCMessageDescriptor client_hello__descriptor; +extern const ProtobufCMessageDescriptor hello_response__descriptor; extern const ProtobufCMessageDescriptor policy_check_request__descriptor; extern const ProtobufCMessageDescriptor policy_accept_message__descriptor; extern const ProtobufCMessageDescriptor policy_reject_message__descriptor; extern const ProtobufCMessageDescriptor policy_error_message__descriptor; -extern const ProtobufCMessageDescriptor policy_check_result__descriptor; +extern const ProtobufCMessageDescriptor intercept_response__descriptor; PROTOBUF_C__END_DECLS diff --git a/src/exec_intercept.c b/src/exec_intercept.c index d5631a062..4d96d38b5 100644 --- a/src/exec_intercept.c +++ b/src/exec_intercept.c @@ -24,7 +24,7 @@ #include #include -#include +#include #if defined(HAVE_STDINT_H) # include @@ -42,6 +42,7 @@ #include "sudo_exec.h" #include "sudo_plugin.h" #include "sudo_plugin_int.h" +#include "sudo_rand.h" #include "intercept.pb-c.h" /* TCSASOFT is a BSD extension that ignores control flags and speed. */ @@ -49,15 +50,6 @@ # define TCSASOFT 0 #endif -static void intercept_cb(int fd, int what, void *v); - -/* Must match start of exec_closure_nopty and monitor_closure. */ -struct intercept_fd_closure { - uint64_t secret; - struct command_details *details; - struct sudo_event_base *evbase; -}; - /* Closure for intercept_cb() */ struct intercept_closure { struct command_details *details; @@ -67,23 +59,68 @@ struct intercept_closure { char **run_argv; /* owned by plugin */ char **run_envp; /* dynamically allocated */ uint8_t *buf; /* dynamically allocated */ - uint64_t secret; size_t len; + int listen_sock; int policy_result; }; +static uint64_t intercept_secret; +static in_port_t intercept_listen_port; +static void intercept_accept_cb(int fd, int what, void *v); +static void intercept_cb(int fd, int what, void *v); + +bool +intercept_setup(int fd, struct sudo_event_base *evbase, + struct command_details *details) +{ + struct intercept_closure *closure; + debug_decl(intercept_setup, SUDO_DEBUG_EXEC); + + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "intercept fd %d\n", fd); + + closure = calloc(1, sizeof(*closure)); + if (closure == NULL) { + sudo_warnx("%s", U_("unable to allocate memory")); + goto bad; + } + + closure->details = details; + /* XXX - add proper state variable */ + closure->policy_result = intercept_secret ? -1 : 1; + closure->listen_sock = -1; + + if (sudo_ev_set(&closure->ev, fd, SUDO_EV_READ, intercept_cb, closure) == -1) { + /* This cannot (currently) fail. */ + sudo_warn("%s", U_("unable to add event to queue")); + goto bad; + } + if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) { + sudo_warn("%s", U_("unable to add event to queue")); + goto bad; + } + + debug_return_bool(true); + +bad: + free(closure); + debug_return_bool(false); +} + /* - * Close intercept fd and free closure. - * Called on EOF from sudo_intercept.so due to program exit. + * Close intercept socket and free closure when we are done with + * the connection. */ static void -intercept_close(int fd, struct intercept_closure *closure) +intercept_connection_close(int fd, struct intercept_closure *closure) { size_t n; - debug_decl(intercept_close, SUDO_DEBUG_EXEC); + debug_decl(intercept_connection_close, SUDO_DEBUG_EXEC); sudo_ev_del(NULL, &closure->ev); close(fd); + if (closure->listen_sock != -1) + close(closure->listen_sock); free(closure->buf); free(closure->command); @@ -102,6 +139,59 @@ intercept_close(int fd, struct intercept_closure *closure) debug_return; } +/* + * Prepare to listen on localhost using an ephemeral port. + * Sets intercept_secret and intercept_listen_port as side effects. + */ +static bool +prepare_listener(struct intercept_closure *closure) +{ + struct sockaddr_in sin; + socklen_t sin_len = sizeof(sin); + int sock; + debug_decl(prepare_listener, SUDO_DEBUG_EXEC); + + /* Secret must be non-zero. */ + do { + intercept_secret = arc4random() | ((uint64_t)arc4random() << 32); + } while (intercept_secret == 0); + + /* Create localhost listener socket (currently AF_INET only). */ + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) { + sudo_warn("socket"); + goto bad; + } + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = 0; + if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) == -1) { + sudo_warn("bind"); + goto bad; + } + if (getsockname(sock, (struct sockaddr *)&sin, &sin_len) == -1) { + sudo_warn("getsockname"); + goto bad; + } + if (listen(sock, SOMAXCONN) == -1) { + sudo_warn("listen"); + goto bad; + } + + closure->listen_sock = sock; + intercept_listen_port = ntohs(sin.sin_port); + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "%s: listening on port %hu", __func__, intercept_listen_port); + + debug_return_bool(true); + +bad: + if (sock != -1) + close(sock); + debug_return_bool(false); +} + static int intercept_check_policy(PolicyCheckRequest *req, struct intercept_closure *closure, const char **errstr) @@ -117,6 +207,13 @@ intercept_check_policy(PolicyCheckRequest *req, *errstr = N_("invalid PolicyCheckRequest"); goto bad; } + if (req->secret != intercept_secret) { + *errstr = N_("invalid PolicyCheckRequest"); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "secret mismatch: got %" PRIu64 ", expected %" PRIu64, req->secret, + intercept_secret); + goto bad; + } if (sudo_debug_needed(SUDO_DEBUG_INFO)) { sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, @@ -255,45 +352,41 @@ bad: } /* - * Read a single message from sudo_intercept.so. + * Read a single message from sudo_intercept.so and unpack it. + * Assumes fd is in blocking mode. */ -static bool -intercept_read(int fd, struct intercept_closure *closure) +static InterceptRequest * +intercept_recv_request(int fd) { - struct sudo_event_base *base = sudo_ev_get_base(&closure->ev); - InterceptMessage *msg = NULL; + InterceptRequest *req = NULL; uint8_t *cp, *buf = NULL; - pid_t saved_pgrp = -1; - struct termios oterm; - uint32_t msg_len; - bool ret = false; - int ttyfd = -1; + uint32_t req_len; ssize_t nread; - debug_decl(intercept_read, SUDO_DEBUG_EXEC); + debug_decl(intercept_recv_request, SUDO_DEBUG_EXEC); /* Read message size (uint32_t in host byte order). */ - nread = read(fd, &msg_len, sizeof(msg_len)); - if (nread != sizeof(msg_len)) { + nread = recv(fd, &req_len, sizeof(req_len), 0); + if (nread != sizeof(req_len)) { if (nread != 0) sudo_warn("read"); goto done; } - if (msg_len > MESSAGE_SIZE_MAX) { - sudo_warnx(U_("client message too large: %zu"), (size_t)msg_len); + if (req_len > MESSAGE_SIZE_MAX) { + sudo_warnx(U_("client request too large: %zu"), (size_t)req_len); goto done; } - if (msg_len > 0) { - size_t rem = msg_len; + if (req_len > 0) { + size_t rem = req_len; - if ((buf = malloc(msg_len)) == NULL) { + if ((buf = malloc(req_len)) == NULL) { sudo_warnx("%s", U_("unable to allocate memory")); goto done; } cp = buf; do { - nread = read(fd, cp, rem); + nread = recv(fd, cp, rem, 0); switch (nread) { case 0: /* EOF, other side must have exited. */ @@ -309,35 +402,83 @@ intercept_read(int fd, struct intercept_closure *closure) } while (rem > 0); } - msg = intercept_message__unpack(NULL, msg_len, buf); - if (msg == NULL) { - sudo_warnx("unable to unpack %s size %zu", "InterceptMessage", - (size_t)msg_len); - goto done; - } - if (msg->type_case != INTERCEPT_MESSAGE__TYPE_POLICY_CHECK_REQ) { - sudo_warnx(U_("unexpected type_case value %d in %s from %s"), - msg->type_case, "InterceptMessage", "sudo_intercept.so"); + req = intercept_request__unpack(NULL, req_len, buf); + if (req == NULL) { + sudo_warnx("unable to unpack %s size %zu", "InterceptRequest", + (size_t)req_len); goto done; } - /* Take back control of the tty, if necessary, for the policy check. */ - ttyfd = open(_PATH_TTY, O_RDWR); - if (ttyfd != -1) { - saved_pgrp = tcgetpgrp(ttyfd); - if (saved_pgrp == -1 || tcsetpgrp(ttyfd, getpgid(0)) == -1 || - tcgetattr(ttyfd, &oterm) == -1) { - close(ttyfd); - ttyfd = -1; +done: + free(buf); + debug_return_ptr(req); +} + +/* + * Read a message from sudo_intercept.so and act on it. + */ +static bool +intercept_read(int fd, struct intercept_closure *closure) +{ + struct sudo_event_base *base = sudo_ev_get_base(&closure->ev); + InterceptRequest *req; + pid_t saved_pgrp = -1; + struct termios oterm; + bool ret = false; + int ttyfd = -1; + debug_decl(intercept_read, SUDO_DEBUG_EXEC); + + req = intercept_recv_request(fd); + if (req == NULL) + goto done; + + switch (req->type_case) { + case INTERCEPT_REQUEST__TYPE_POLICY_CHECK_REQ: + if (closure->policy_result != -1) { + /* Only a single policy check request is allowed. */ + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "got another PolicyCheckRequest on the socket (%d)", + closure->policy_result); + goto done; } - } - closure->policy_result = intercept_check_policy(msg->u.policy_check_req, - closure, &closure->errstr); + /* Take back control of the tty, if necessary, for the policy check. */ + ttyfd = open(_PATH_TTY, O_RDWR); + if (ttyfd != -1) { + saved_pgrp = tcgetpgrp(ttyfd); + if (saved_pgrp == -1 || tcsetpgrp(ttyfd, getpgid(0)) == -1 || + tcgetattr(ttyfd, &oterm) == -1) { + close(ttyfd); + ttyfd = -1; + } + } - if (ttyfd != -1) { - (void)tcsetattr(ttyfd, TCSASOFT|TCSAFLUSH, &oterm); - (void)tcsetpgrp(ttyfd, saved_pgrp); + closure->policy_result = intercept_check_policy(req->u.policy_check_req, + closure, &closure->errstr); + + if (ttyfd != -1) { + (void)tcsetattr(ttyfd, TCSASOFT|TCSAFLUSH, &oterm); + (void)tcsetpgrp(ttyfd, saved_pgrp); + } + break; + case INTERCEPT_REQUEST__TYPE_HELLO: + if (closure->policy_result != 1) { + /* Only accept hello on a socket with an accepted command. */ + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "got ClientHello without an accepted command"); + goto done; + } + + /* Start listener after first Hello. */ + if (intercept_secret == 0 && !prepare_listener(closure)) + goto done; + + closure->policy_result = 2; /* XXX */ + break; + default: + sudo_warnx(U_("unexpected type_case value %d in %s from %s"), + req->type_case, "InterceptRequest", "sudo_intercept.so"); + goto done; } /* Switch event to write mode for the reply. */ @@ -356,38 +497,37 @@ intercept_read(int fd, struct intercept_closure *closure) done: if (ttyfd != -1) close(ttyfd); - intercept_message__free_unpacked(msg, NULL); - free(buf); + intercept_request__free_unpacked(req, NULL); debug_return_bool(ret); } static bool -fmt_policy_check_result(PolicyCheckResult *res, struct intercept_closure *closure) +fmt_intercept_response(InterceptResponse *resp, + struct intercept_closure *closure) { - uint32_t msg_len; + uint32_t resp_len; bool ret = false; - debug_decl(fmt_policy_check_result, SUDO_DEBUG_EXEC); + debug_decl(fmt_intercept_response, SUDO_DEBUG_EXEC); - res->secret = closure->secret; - closure->len = policy_check_result__get_packed_size(res); + closure->len = intercept_response__get_packed_size(resp); if (closure->len > MESSAGE_SIZE_MAX) { sudo_warnx(U_("server message too large: %zu"), closure->len); goto done; } /* Wire message size is used for length encoding, precedes message. */ - msg_len = closure->len; - closure->len += sizeof(msg_len); + resp_len = closure->len; + closure->len += sizeof(resp_len); sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, - "size + PolicyCheckResult %zu bytes", closure->len); + "size + InterceptResponse %zu bytes", closure->len); if ((closure->buf = malloc(closure->len)) == NULL) { sudo_warnx("%s", U_("unable to allocate memory")); goto done; } - memcpy(closure->buf, &msg_len, sizeof(msg_len)); - policy_check_result__pack(res, closure->buf + sizeof(msg_len)); + memcpy(closure->buf, &resp_len, sizeof(resp_len)); + intercept_response__pack(resp, closure->buf + sizeof(resp_len)); ret = true; @@ -395,11 +535,27 @@ done: debug_return_bool(ret); } +static bool +fmt_hello_response(struct intercept_closure *closure) +{ + HelloResponse hello_resp = HELLO_RESPONSE__INIT; + InterceptResponse resp = INTERCEPT_RESPONSE__INIT; + debug_decl(fmt_hello_response, SUDO_DEBUG_EXEC); + + hello_resp.portno = intercept_listen_port; + hello_resp.secret = intercept_secret; + + resp.u.hello_resp = &hello_resp; + resp.type_case = INTERCEPT_RESPONSE__TYPE_HELLO_RESP; + + debug_return_bool(fmt_intercept_response(&resp, closure)); +} + static bool fmt_accept_message(struct intercept_closure *closure) { PolicyAcceptMessage msg = POLICY_ACCEPT_MESSAGE__INIT; - PolicyCheckResult res = POLICY_CHECK_RESULT__INIT; + InterceptResponse resp = INTERCEPT_RESPONSE__INIT; size_t n; debug_decl(fmt_accept_message, SUDO_DEBUG_EXEC); @@ -413,40 +569,40 @@ fmt_accept_message(struct intercept_closure *closure) continue; msg.n_run_envp = n; - res.u.accept_msg = &msg; - res.type_case = POLICY_CHECK_RESULT__TYPE_ACCEPT_MSG; + resp.u.accept_msg = &msg; + resp.type_case = INTERCEPT_RESPONSE__TYPE_ACCEPT_MSG; - debug_return_bool(fmt_policy_check_result(&res, closure)); + debug_return_bool(fmt_intercept_response(&resp, closure)); } static bool fmt_reject_message(struct intercept_closure *closure) { PolicyRejectMessage msg = POLICY_REJECT_MESSAGE__INIT; - PolicyCheckResult res = POLICY_CHECK_RESULT__INIT; + InterceptResponse resp = INTERCEPT_RESPONSE__INIT; debug_decl(fmt_reject_message, SUDO_DEBUG_EXEC); msg.reject_message = (char *)closure->errstr; - res.u.reject_msg = &msg; - res.type_case = POLICY_CHECK_RESULT__TYPE_REJECT_MSG; + resp.u.reject_msg = &msg; + resp.type_case = INTERCEPT_RESPONSE__TYPE_REJECT_MSG; - debug_return_bool(fmt_policy_check_result(&res, closure)); + debug_return_bool(fmt_intercept_response(&resp, closure)); } static bool fmt_error_message(struct intercept_closure *closure) { PolicyErrorMessage msg = POLICY_ERROR_MESSAGE__INIT; - PolicyCheckResult res = POLICY_CHECK_RESULT__INIT; + InterceptResponse resp = INTERCEPT_RESPONSE__INIT; debug_decl(fmt_error_message, SUDO_DEBUG_EXEC); msg.error_message = (char *)closure->errstr; - res.u.error_msg = &msg; - res.type_case = POLICY_CHECK_RESULT__TYPE_ERROR_MSG; + resp.u.error_msg = &msg; + resp.type_case = INTERCEPT_RESPONSE__TYPE_ERROR_MSG; - debug_return_bool(fmt_policy_check_result(&res, closure)); + debug_return_bool(fmt_intercept_response(&resp, closure)); } /* @@ -455,13 +611,19 @@ fmt_error_message(struct intercept_closure *closure) static bool intercept_write(int fd, struct intercept_closure *closure) { - size_t rem; - uint8_t *cp; + struct sudo_event_base *evbase = sudo_ev_get_base(&closure->ev); ssize_t nwritten; bool ret = false; + uint8_t *cp; + size_t rem; debug_decl(intercept_write, SUDO_DEBUG_EXEC); + /* XXX - proper state variable */ switch (closure->policy_result) { + case 2: + if (!fmt_hello_response(closure)) + goto done; + break; case 1: if (!fmt_accept_message(closure)) goto done; @@ -479,15 +641,55 @@ intercept_write(int fd, struct intercept_closure *closure) cp = closure->buf; rem = closure->len; do { - nwritten = write(fd, cp, rem); + nwritten = send(fd, cp, rem, 0); if (nwritten == -1) { - sudo_warn("write"); + sudo_warn("send"); goto done; } cp += nwritten; rem -= nwritten; } while (rem > 0); + switch (closure->policy_result) { + case 1: + /* Switch event to read mode for sudo_intercept.so ctor. */ + if (sudo_ev_set(&closure->ev, fd, SUDO_EV_READ, intercept_cb, closure) == -1) { + /* This cannot (currently) fail. */ + sudo_warn("%s", U_("unable to add event to queue")); + goto done; + } + if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) { + sudo_warn("%s", U_("unable to add event to queue")); + goto done; + } + break; + case 2: + if (closure->listen_sock != -1) { + /* Re-use intercept_event for the listener. */ + if (sudo_ev_set(&closure->ev, closure->listen_sock, SUDO_EV_READ|SUDO_EV_PERSIST, intercept_accept_cb, closure) == -1) { + /* This cannot (currently) fail. */ + sudo_warn("%s", U_("unable to add event to queue")); + goto done; + } + if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) { + sudo_warn("%s", U_("unable to add event to queue")); + goto done; + } + close(fd); + + /* Reset bits of closure we used for Hello. */ + free(closure->buf); + closure->buf = NULL; + closure->len = 0; + closure->listen_sock = -1; + break; + } + FALLTHROUGH; + default: + /* Done with this connection. */ + intercept_connection_close(fd, closure); + } + ret = true; done: @@ -513,115 +715,40 @@ intercept_cb(int fd, int what, void *v) break; } - if (!success || what == SUDO_EV_WRITE) { - intercept_close(fd, closure); - } + if (!success) + intercept_connection_close(fd, closure); debug_return; } /* - * Accept a single fd passed from the child to use for policy checks. - * This acts a bit like accept() in reverse since the client allocates - * the socketpair() that is used for the actual communication. + * Accept a new connection from the client and fill in a client closure. + * Registers a new event for the connection. */ -void -intercept_fd_cb(int fd, int what, void *v) +static void +intercept_accept_cb(int fd, int what, void *v) { - struct intercept_closure *closure = NULL; - struct intercept_fd_closure *fdc = v; - struct msghdr msg; -#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && HAVE_STRUCT_MSGHDR_MSG_CONTROL == 1 - union { - struct cmsghdr hdr; - char buf[CMSG_SPACE(sizeof(int))]; - } cmsgbuf; - struct cmsghdr *cmsg; -#endif - struct iovec iov[1]; - int newfd = -1; - char ch; - debug_decl(intercept_fd_cb, SUDO_DEBUG_EXEC); + struct intercept_closure *closure = v; + struct sudo_event_base *evbase = sudo_ev_get_base(&closure->ev); + struct sockaddr_in sin; + socklen_t sin_len = sizeof(sin); + int client_sock; + debug_decl(intercept_accept_cb, SUDO_DEBUG_EXEC); - /* - * We send a single byte of data along with the fd; some systems - * don't support sending file descriptors without data. - * Note that the intercept fd is *blocking*. - */ - iov[0].iov_base = &ch; - iov[0].iov_len = 1; - memset(&msg, 0, sizeof(msg)); - msg.msg_iov = iov; - msg.msg_iovlen = 1; -#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && HAVE_STRUCT_MSGHDR_MSG_CONTROL == 1 - memset(&cmsgbuf, 0, sizeof(cmsgbuf)); - msg.msg_control = &cmsgbuf.buf; - msg.msg_controllen = sizeof(cmsgbuf.buf); -#else - msg.msg_accrights = (caddr_t)&newfd; - msg.msg_accrightslen = sizeof(newfd); -#endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL */ - - switch (recvmsg(fd, &msg, 0)) { - case -1: - if (errno != EAGAIN && errno != EINTR) - sudo_warn("recvmsg"); - goto bad; - case 0: - /* EOF */ - goto bad; - default: - break; - } - - if (ch == INTERCEPT_REQ_SEC) { - /* Client requested secret from ctor, no fd is present. */ - if (write(fd, &fdc->secret, sizeof(fdc->secret)) != sizeof(fdc->secret)) - goto bad; - debug_return; - } - -#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && HAVE_STRUCT_MSGHDR_MSG_CONTROL == 1 - cmsg = CMSG_FIRSTHDR(&msg); - if (cmsg == NULL) { - sudo_warnx(U_("%s: missing message header"), __func__); + client_sock = accept(fd, (struct sockaddr *)&sin, &sin_len); + if (client_sock == -1) { + sudo_warn("accept"); goto bad; } - if (cmsg->cmsg_type != SCM_RIGHTS) { - sudo_warnx(U_("%s: expected message type %d, got %d"), __func__, - SCM_RIGHTS, cmsg->cmsg_type); - goto bad; - } - memcpy(&newfd, CMSG_DATA(cmsg), sizeof(newfd)); -#else - if (msg.msg_accrightslen != sizeof(newfd)) { - sudo_warnx(U_("%s: missing message header"), __func__); - goto bad; - } -#endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL */ - - closure = calloc(1, sizeof(*closure)); - if (closure == NULL) { - sudo_warnx("%s", U_("unable to allocate memory")); - goto bad; - } - closure->secret = fdc->secret; - closure->details = fdc->details; - - if (sudo_ev_set(&closure->ev, newfd, SUDO_EV_READ, intercept_cb, closure) == -1) { - sudo_warn("%s", U_("unable to add event to queue")); - goto bad; - } - if (sudo_ev_add(fdc->evbase, &closure->ev, NULL, false) == -1) { - sudo_warn("%s", U_("unable to add event to queue")); + if (!intercept_setup(client_sock, evbase, closure->details)) { goto bad; } debug_return; + bad: - if (newfd != -1) - close(newfd); - free(closure); + if (client_sock != -1) + close(client_sock); debug_return; } diff --git a/src/exec_nopty.c b/src/exec_nopty.c index a0853a910..8f2f7a297 100644 --- a/src/exec_nopty.c +++ b/src/exec_nopty.c @@ -43,15 +43,11 @@ #include "sudo_exec.h" #include "sudo_plugin.h" #include "sudo_plugin_int.h" -#include "sudo_rand.h" -/* Note that details and evbase must come first. */ struct exec_closure_nopty { - uint64_t secret; struct command_details *details; struct sudo_event_base *evbase; struct sudo_event *errpipe_event; - struct sudo_event *intercept_event; struct sudo_event *sigint_event; struct sudo_event *sigquit_event; struct sudo_event *sigtstp_event; @@ -203,13 +199,11 @@ signal_cb_nopty(int signo, int what, void *v) */ static void fill_exec_closure_nopty(struct exec_closure_nopty *ec, - struct command_status *cstat, struct command_details *details, - int intercept_fd, int errfd) + struct command_status *cstat, struct command_details *details, int errfd) { debug_decl(fill_exec_closure_nopty, SUDO_DEBUG_EXEC); /* Fill in the non-event part of the closure. */ - ec->secret = arc4random() | ((uint64_t)arc4random() << 32); ec->ppgrp = getpgrp(); ec->cstat = cstat; ec->details = details; @@ -227,17 +221,6 @@ fill_exec_closure_nopty(struct exec_closure_nopty *ec, sudo_fatal("%s", U_("unable to add event to queue")); sudo_debug_printf(SUDO_DEBUG_INFO, "error pipe fd %d\n", errfd); - /* Event for sudo_intercept.so (optional). */ - if (intercept_fd != -1) { - ec->intercept_event = sudo_ev_alloc(intercept_fd, - SUDO_EV_READ|SUDO_EV_PERSIST, intercept_fd_cb, ec); - if (ec->intercept_event == NULL) - sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); - if (sudo_ev_add(ec->evbase, ec->intercept_event, NULL, false) == -1) - sudo_fatal("%s", U_("unable to add event to queue")); - sudo_debug_printf(SUDO_DEBUG_INFO, "intercept fd %d\n", intercept_fd); - } - /* Events for local signals. */ ec->sigint_event = sudo_ev_alloc(SIGINT, SUDO_EV_SIGINFO, signal_cb_nopty, ec); @@ -387,7 +370,7 @@ exec_nopty(struct command_details *details, struct command_status *cstat) * This must be inherited across exec, hence no FD_CLOEXEC. */ if (ISSET(details->flags, CD_INTERCEPT|CD_LOG_CHILDREN)) { - if (socketpair(PF_UNIX, SOCK_DGRAM, 0, intercept_sv) == -1) + if (socketpair(PF_UNIX, SOCK_STREAM, 0, intercept_sv) == -1) sudo_fatal("%s", U_("unable to create sockets")); } @@ -454,7 +437,13 @@ exec_nopty(struct command_details *details, struct command_status *cstat) * Fill in exec closure, allocate event base, signal events and * the error pipe event. */ - fill_exec_closure_nopty(&ec, cstat, details, intercept_sv[0], errpipe[0]); + fill_exec_closure_nopty(&ec, cstat, details, errpipe[0]); + + /* Create event and closure for intercept mode. */ + if (intercept_sv[0] != -1) { + if (!intercept_setup(intercept_sv[0], ec.evbase, details)) + exit(EXIT_FAILURE); + } /* Restore signal mask now that signal handlers are setup. */ sigprocmask(SIG_SETMASK, &oset, NULL); diff --git a/src/exec_pty.c b/src/exec_pty.c index ed2171dee..4b1f7079d 100644 --- a/src/exec_pty.c +++ b/src/exec_pty.c @@ -47,7 +47,6 @@ #include "sudo_exec.h" #include "sudo_plugin.h" #include "sudo_plugin_int.h" -#include "sudo_rand.h" /* Evaluates to true if the event has /dev/tty as its fd. */ #define USERTTY_EVENT(_ev) (sudo_ev_get_fd((_ev)) == io_fds[SFD_USERTTY]) @@ -62,14 +61,11 @@ struct monitor_message { }; TAILQ_HEAD(monitor_message_list, monitor_message); -/* Note that details and evbase must come first. */ struct exec_closure_pty { - uint64_t secret; struct command_details *details; struct sudo_event_base *evbase; struct sudo_event *backchannel_event; struct sudo_event *fwdchannel_event; - struct sudo_event *intercept_event; struct sudo_event *sigint_event; struct sudo_event *sigquit_event; struct sudo_event *sigtstp_event; @@ -1207,13 +1203,11 @@ fwdchannel_cb(int sock, int what, void *v) */ static void fill_exec_closure_pty(struct exec_closure_pty *ec, struct command_status *cstat, - struct command_details *details, pid_t ppgrp, int backchannel, - int intercept_fd) + struct command_details *details, pid_t ppgrp, int backchannel) { debug_decl(fill_exec_closure_pty, SUDO_DEBUG_EXEC); /* Fill in the non-event part of the closure. */ - ec->secret = arc4random() | ((uint64_t)arc4random() << 32); ec->cmnd_pid = -1; ec->ppgrp = ppgrp; ec->cstat = cstat; @@ -1239,17 +1233,6 @@ fill_exec_closure_pty(struct exec_closure_pty *ec, struct command_status *cstat, sudo_fatal("%s", U_("unable to add event to queue")); sudo_debug_printf(SUDO_DEBUG_INFO, "backchannel fd %d\n", backchannel); - /* Event for sudo_intercept.so (optional). */ - if (intercept_fd != -1) { - ec->intercept_event = sudo_ev_alloc(intercept_fd, - SUDO_EV_READ|SUDO_EV_PERSIST, intercept_fd_cb, ec); - if (ec->intercept_event == NULL) - sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); - if (sudo_ev_add(ec->evbase, ec->intercept_event, NULL, false) == -1) - sudo_fatal("%s", U_("unable to add event to queue")); - sudo_debug_printf(SUDO_DEBUG_INFO, "intercept fd %d\n", intercept_fd); - } - /* Events for local signals. */ ec->sigint_event = sudo_ev_alloc(SIGINT, SUDO_EV_SIGINFO, signal_cb_pty, ec); @@ -1409,7 +1392,7 @@ exec_pty(struct command_details *details, struct command_status *cstat) * This must be inherited across exec, hence no FD_CLOEXEC. */ if (ISSET(details->flags, CD_INTERCEPT|CD_LOG_CHILDREN)) { - if (socketpair(PF_UNIX, SOCK_DGRAM, 0, intercept_sv) == -1) + if (socketpair(PF_UNIX, SOCK_STREAM, 0, intercept_sv) == -1) sudo_fatal("%s", U_("unable to create sockets")); } @@ -1652,7 +1635,13 @@ exec_pty(struct command_details *details, struct command_status *cstat) * Fill in exec closure, allocate event base, signal events and * the backchannel event. */ - fill_exec_closure_pty(&ec, cstat, details, ppgrp, sv[0], intercept_sv[0]); + fill_exec_closure_pty(&ec, cstat, details, ppgrp, sv[0]); + + /* Create event and closure for intercept mode. */ + if (intercept_sv[0] != -1) { + if (!intercept_setup(intercept_sv[0], ec.evbase, details)) + exit(EXIT_FAILURE); + } /* Restore signal mask now that signal handlers are setup. */ sigprocmask(SIG_SETMASK, &oset, NULL); diff --git a/src/intercept.pb-c.c b/src/intercept.pb-c.c index 6194d1c12..9e8df76db 100644 --- a/src/intercept.pb-c.c +++ b/src/intercept.pb-c.c @@ -7,49 +7,139 @@ #endif #include "intercept.pb-c.h" -void intercept_message__init - (InterceptMessage *message) +void intercept_request__init + (InterceptRequest *message) { - static const InterceptMessage init_value = INTERCEPT_MESSAGE__INIT; + static const InterceptRequest init_value = INTERCEPT_REQUEST__INIT; *message = init_value; } -size_t intercept_message__get_packed_size - (const InterceptMessage *message) +size_t intercept_request__get_packed_size + (const InterceptRequest *message) { - assert(message->base.descriptor == &intercept_message__descriptor); + assert(message->base.descriptor == &intercept_request__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } -size_t intercept_message__pack - (const InterceptMessage *message, +size_t intercept_request__pack + (const InterceptRequest *message, uint8_t *out) { - assert(message->base.descriptor == &intercept_message__descriptor); + assert(message->base.descriptor == &intercept_request__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } -size_t intercept_message__pack_to_buffer - (const InterceptMessage *message, +size_t intercept_request__pack_to_buffer + (const InterceptRequest *message, ProtobufCBuffer *buffer) { - assert(message->base.descriptor == &intercept_message__descriptor); + assert(message->base.descriptor == &intercept_request__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } -InterceptMessage * - intercept_message__unpack +InterceptRequest * + intercept_request__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { - return (InterceptMessage *) - protobuf_c_message_unpack (&intercept_message__descriptor, + return (InterceptRequest *) + protobuf_c_message_unpack (&intercept_request__descriptor, allocator, len, data); } -void intercept_message__free_unpacked - (InterceptMessage *message, +void intercept_request__free_unpacked + (InterceptRequest *message, ProtobufCAllocator *allocator) { if(!message) return; - assert(message->base.descriptor == &intercept_message__descriptor); + assert(message->base.descriptor == &intercept_request__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void client_hello__init + (ClientHello *message) +{ + static const ClientHello init_value = CLIENT_HELLO__INIT; + *message = init_value; +} +size_t client_hello__get_packed_size + (const ClientHello *message) +{ + assert(message->base.descriptor == &client_hello__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t client_hello__pack + (const ClientHello *message, + uint8_t *out) +{ + assert(message->base.descriptor == &client_hello__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t client_hello__pack_to_buffer + (const ClientHello *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &client_hello__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +ClientHello * + client_hello__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (ClientHello *) + protobuf_c_message_unpack (&client_hello__descriptor, + allocator, len, data); +} +void client_hello__free_unpacked + (ClientHello *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &client_hello__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void hello_response__init + (HelloResponse *message) +{ + static const HelloResponse init_value = HELLO_RESPONSE__INIT; + *message = init_value; +} +size_t hello_response__get_packed_size + (const HelloResponse *message) +{ + assert(message->base.descriptor == &hello_response__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t hello_response__pack + (const HelloResponse *message, + uint8_t *out) +{ + assert(message->base.descriptor == &hello_response__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t hello_response__pack_to_buffer + (const HelloResponse *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &hello_response__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +HelloResponse * + hello_response__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (HelloResponse *) + protobuf_c_message_unpack (&hello_response__descriptor, + allocator, len, data); +} +void hello_response__free_unpacked + (HelloResponse *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &hello_response__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } void policy_check_request__init @@ -232,90 +322,192 @@ void policy_error_message__free_unpacked assert(message->base.descriptor == &policy_error_message__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } -void policy_check_result__init - (PolicyCheckResult *message) +void intercept_response__init + (InterceptResponse *message) { - static const PolicyCheckResult init_value = POLICY_CHECK_RESULT__INIT; + static const InterceptResponse init_value = INTERCEPT_RESPONSE__INIT; *message = init_value; } -size_t policy_check_result__get_packed_size - (const PolicyCheckResult *message) +size_t intercept_response__get_packed_size + (const InterceptResponse *message) { - assert(message->base.descriptor == &policy_check_result__descriptor); + assert(message->base.descriptor == &intercept_response__descriptor); return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); } -size_t policy_check_result__pack - (const PolicyCheckResult *message, +size_t intercept_response__pack + (const InterceptResponse *message, uint8_t *out) { - assert(message->base.descriptor == &policy_check_result__descriptor); + assert(message->base.descriptor == &intercept_response__descriptor); return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); } -size_t policy_check_result__pack_to_buffer - (const PolicyCheckResult *message, +size_t intercept_response__pack_to_buffer + (const InterceptResponse *message, ProtobufCBuffer *buffer) { - assert(message->base.descriptor == &policy_check_result__descriptor); + assert(message->base.descriptor == &intercept_response__descriptor); return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); } -PolicyCheckResult * - policy_check_result__unpack +InterceptResponse * + intercept_response__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { - return (PolicyCheckResult *) - protobuf_c_message_unpack (&policy_check_result__descriptor, + return (InterceptResponse *) + protobuf_c_message_unpack (&intercept_response__descriptor, allocator, len, data); } -void policy_check_result__free_unpacked - (PolicyCheckResult *message, +void intercept_response__free_unpacked + (InterceptResponse *message, ProtobufCAllocator *allocator) { if(!message) return; - assert(message->base.descriptor == &policy_check_result__descriptor); + assert(message->base.descriptor == &intercept_response__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } -static const ProtobufCFieldDescriptor intercept_message__field_descriptors[1] = +static const ProtobufCFieldDescriptor intercept_request__field_descriptors[2] = { { "policy_check_req", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, - offsetof(InterceptMessage, type_case), - offsetof(InterceptMessage, u.policy_check_req), + offsetof(InterceptRequest, type_case), + offsetof(InterceptRequest, u.policy_check_req), &policy_check_request__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, + { + "hello", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(InterceptRequest, type_case), + offsetof(InterceptRequest, u.hello), + &client_hello__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, }; -static const unsigned intercept_message__field_indices_by_name[] = { +static const unsigned intercept_request__field_indices_by_name[] = { + 1, /* field[1] = hello */ 0, /* field[0] = policy_check_req */ }; -static const ProtobufCIntRange intercept_message__number_ranges[1 + 1] = +static const ProtobufCIntRange intercept_request__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 2 } +}; +const ProtobufCMessageDescriptor intercept_request__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "InterceptRequest", + "InterceptRequest", + "InterceptRequest", + "", + sizeof(InterceptRequest), + 2, + intercept_request__field_descriptors, + intercept_request__field_indices_by_name, + 1, intercept_request__number_ranges, + (ProtobufCMessageInit) intercept_request__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor client_hello__field_descriptors[1] = +{ + { + "pid", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT32, + 0, /* quantifier_offset */ + offsetof(ClientHello, pid), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned client_hello__field_indices_by_name[] = { + 0, /* field[0] = pid */ +}; +static const ProtobufCIntRange client_hello__number_ranges[1 + 1] = { { 1, 0 }, { 0, 1 } }; -const ProtobufCMessageDescriptor intercept_message__descriptor = +const ProtobufCMessageDescriptor client_hello__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "InterceptMessage", - "InterceptMessage", - "InterceptMessage", + "ClientHello", + "ClientHello", + "ClientHello", "", - sizeof(InterceptMessage), + sizeof(ClientHello), 1, - intercept_message__field_descriptors, - intercept_message__field_indices_by_name, - 1, intercept_message__number_ranges, - (ProtobufCMessageInit) intercept_message__init, + client_hello__field_descriptors, + client_hello__field_indices_by_name, + 1, client_hello__number_ranges, + (ProtobufCMessageInit) client_hello__init, NULL,NULL,NULL /* reserved[123] */ }; -static const ProtobufCFieldDescriptor policy_check_request__field_descriptors[3] = +static const ProtobufCFieldDescriptor hello_response__field_descriptors[2] = +{ + { + "secret", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_FIXED64, + 0, /* quantifier_offset */ + offsetof(HelloResponse, secret), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "portno", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT32, + 0, /* quantifier_offset */ + offsetof(HelloResponse, portno), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned hello_response__field_indices_by_name[] = { + 1, /* field[1] = portno */ + 0, /* field[0] = secret */ +}; +static const ProtobufCIntRange hello_response__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 2 } +}; +const ProtobufCMessageDescriptor hello_response__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "HelloResponse", + "HelloResponse", + "HelloResponse", + "", + sizeof(HelloResponse), + 2, + hello_response__field_descriptors, + hello_response__field_indices_by_name, + 1, hello_response__number_ranges, + (ProtobufCMessageInit) hello_response__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor policy_check_request__field_descriptors[5] = { { "command", @@ -353,16 +545,42 @@ static const ProtobufCFieldDescriptor policy_check_request__field_descriptors[3] 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, + { + "intercept_fd", + 4, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT32, + 0, /* quantifier_offset */ + offsetof(PolicyCheckRequest, intercept_fd), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "secret", + 5, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_FIXED64, + 0, /* quantifier_offset */ + offsetof(PolicyCheckRequest, secret), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, }; static const unsigned policy_check_request__field_indices_by_name[] = { 1, /* field[1] = argv */ 0, /* field[0] = command */ 2, /* field[2] = envp */ + 3, /* field[3] = intercept_fd */ + 4, /* field[4] = secret */ }; static const ProtobufCIntRange policy_check_request__number_ranges[1 + 1] = { { 1, 0 }, - { 0, 3 } + { 0, 5 } }; const ProtobufCMessageDescriptor policy_check_request__descriptor = { @@ -372,7 +590,7 @@ const ProtobufCMessageDescriptor policy_check_request__descriptor = "PolicyCheckRequest", "", sizeof(PolicyCheckRequest), - 3, + 5, policy_check_request__field_descriptors, policy_check_request__field_indices_by_name, 1, policy_check_request__number_ranges, @@ -519,15 +737,27 @@ const ProtobufCMessageDescriptor policy_error_message__descriptor = (ProtobufCMessageInit) policy_error_message__init, NULL,NULL,NULL /* reserved[123] */ }; -static const ProtobufCFieldDescriptor policy_check_result__field_descriptors[4] = +static const ProtobufCFieldDescriptor intercept_response__field_descriptors[4] = { { - "accept_msg", + "hello_resp", 1, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, - offsetof(PolicyCheckResult, type_case), - offsetof(PolicyCheckResult, u.accept_msg), + offsetof(InterceptResponse, type_case), + offsetof(InterceptResponse, u.hello_resp), + &hello_response__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "accept_msg", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(InterceptResponse, type_case), + offsetof(InterceptResponse, u.accept_msg), &policy_accept_message__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ @@ -535,11 +765,11 @@ static const ProtobufCFieldDescriptor policy_check_result__field_descriptors[4] }, { "reject_msg", - 2, + 3, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, - offsetof(PolicyCheckResult, type_case), - offsetof(PolicyCheckResult, u.reject_msg), + offsetof(InterceptResponse, type_case), + offsetof(InterceptResponse, u.reject_msg), &policy_reject_message__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ @@ -547,52 +777,40 @@ static const ProtobufCFieldDescriptor policy_check_result__field_descriptors[4] }, { "error_msg", - 3, + 4, PROTOBUF_C_LABEL_NONE, PROTOBUF_C_TYPE_MESSAGE, - offsetof(PolicyCheckResult, type_case), - offsetof(PolicyCheckResult, u.error_msg), + offsetof(InterceptResponse, type_case), + offsetof(InterceptResponse, u.error_msg), &policy_error_message__descriptor, NULL, 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, - { - "secret", - 4, - PROTOBUF_C_LABEL_NONE, - PROTOBUF_C_TYPE_FIXED64, - 0, /* quantifier_offset */ - offsetof(PolicyCheckResult, secret), - NULL, - NULL, - 0, /* flags */ - 0,NULL,NULL /* reserved1,reserved2, etc */ - }, }; -static const unsigned policy_check_result__field_indices_by_name[] = { - 0, /* field[0] = accept_msg */ - 2, /* field[2] = error_msg */ - 1, /* field[1] = reject_msg */ - 3, /* field[3] = secret */ +static const unsigned intercept_response__field_indices_by_name[] = { + 1, /* field[1] = accept_msg */ + 3, /* field[3] = error_msg */ + 0, /* field[0] = hello_resp */ + 2, /* field[2] = reject_msg */ }; -static const ProtobufCIntRange policy_check_result__number_ranges[1 + 1] = +static const ProtobufCIntRange intercept_response__number_ranges[1 + 1] = { { 1, 0 }, { 0, 4 } }; -const ProtobufCMessageDescriptor policy_check_result__descriptor = +const ProtobufCMessageDescriptor intercept_response__descriptor = { PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, - "PolicyCheckResult", - "PolicyCheckResult", - "PolicyCheckResult", + "InterceptResponse", + "InterceptResponse", + "InterceptResponse", "", - sizeof(PolicyCheckResult), + sizeof(InterceptResponse), 4, - policy_check_result__field_descriptors, - policy_check_result__field_indices_by_name, - 1, policy_check_result__number_ranges, - (ProtobufCMessageInit) policy_check_result__init, + intercept_response__field_descriptors, + intercept_response__field_indices_by_name, + 1, intercept_response__number_ranges, + (ProtobufCMessageInit) intercept_response__init, NULL,NULL,NULL /* reserved[123] */ }; diff --git a/src/intercept.proto b/src/intercept.proto index ab0611a76..7d7ef1c53 100644 --- a/src/intercept.proto +++ b/src/intercept.proto @@ -4,14 +4,33 @@ syntax = "proto3"; * Intercept message from sudo_intercept.so. Messages on the * wire are prefixed with a 32-bit size in network byte order. */ -message InterceptMessage { +message InterceptRequest { oneof type { PolicyCheckRequest policy_check_req = 1; + ClientHello hello = 2; } } +/* + * Hello message from sudo_intercept.so to main sudo process. + * Sudo sends back the secret and localhost port number. + */ +message ClientHello { + int32 pid = 1; +} + +/* + * Sudo response to a ClientHello from sudo_intercept.so. + * The client uses the port number and secret to connect back to sudo. + */ +message HelloResponse { + fixed64 secret = 1; + int32 portno = 2; +} + /* * Policy check request from sudo_intercept.so. + * Must include the correct secret value. * Note that the plugin API only currently supports passing * the new environment in to the open() function. */ @@ -19,6 +38,8 @@ message PolicyCheckRequest { string command = 1; repeated string argv = 2; repeated string envp = 3; + int32 intercept_fd = 4; + fixed64 secret = 5; } message PolicyAcceptMessage { @@ -36,13 +57,13 @@ message PolicyErrorMessage { } /* - * Policy check result sent back to sudo_intercept.so. + * Response sent back to sudo_intercept.so. */ -message PolicyCheckResult { +message InterceptResponse { oneof type { - PolicyAcceptMessage accept_msg = 1; - PolicyRejectMessage reject_msg = 2; - PolicyErrorMessage error_msg = 3; + HelloResponse hello_resp = 1; + PolicyAcceptMessage accept_msg = 2; + PolicyRejectMessage reject_msg = 3; + PolicyErrorMessage error_msg = 4; } - fixed64 secret = 4; } diff --git a/src/sudo_exec.h b/src/sudo_exec.h index 41b4eae67..3b8b9acf1 100644 --- a/src/sudo_exec.h +++ b/src/sudo_exec.h @@ -80,7 +80,6 @@ #define SESH_ERR_SOME_FILES 33 /* copy error, some files copied */ #define INTERCEPT_FD_MIN 64 /* minimum fd so shell won't close it */ -#define INTERCEPT_REQ_SEC 42 /* request intercept secret */ #define MESSAGE_SIZE_MAX 2097152 /* 2Mib max intercept message size */ /* @@ -88,19 +87,22 @@ */ struct command_details; struct command_status; +struct sudo_event_base; struct stat; /* exec.c */ void exec_cmnd(struct command_details *details, int intercept_fd, int errfd); void terminate_command(pid_t pid, bool use_pgrp); bool sudo_terminated(struct command_status *cstat); -void intercept_fd_cb(int fd, int what, void *v); /* exec_common.c */ int sudo_execve(int fd, const char *path, char *const argv[], char *envp[], int intercept_fd, int flags); char **disable_execute(char *envp[], const char *dso); char **enable_monitor(char *envp[], const char *dso); +/* exec_intercept.c */ +bool intercept_setup(int fd, struct sudo_event_base *evbase, struct command_details *details); + /* exec_nopty.c */ void exec_nopty(struct command_details *details, struct command_status *cstat); diff --git a/src/sudo_intercept_common.c b/src/sudo_intercept_common.c index 85d3db27a..d8b3e3a3e 100644 --- a/src/sudo_intercept_common.c +++ b/src/sudo_intercept_common.c @@ -25,6 +25,7 @@ #include #include +#include #if defined(HAVE_STDINT_H) # include @@ -57,102 +58,49 @@ extern char **environ; -static int intercept_sock = -1; static uint64_t secret; +static in_port_t intercept_port; -/* - * Look up SUDO_INTERCEPT_FD in the environment. - * This function is run when the shared library is loaded. - */ -__attribute__((constructor)) static void -sudo_interposer_init(void) +/* Send entire request to sudo (blocking). */ +static bool +send_req(int sock, const uint8_t *buf, size_t len) { - static bool initialized; - char **p; - debug_decl(sudo_interposer_init, SUDO_DEBUG_EXEC); + const uint8_t *cp = buf; + ssize_t nwritten; + debug_decl(send_req, SUDO_DEBUG_EXEC); - if (!initialized) { - initialized = true; - - /* Read debug section of sudo.conf and init debugging. */ - if (sudo_conf_read(NULL, SUDO_CONF_DEBUG) != -1) { - sudo_debug_register("sudo_intercept.so", NULL, NULL, - sudo_conf_debug_files("sudo_intercept.so")); + do { + nwritten = send(sock, cp, len, 0); + if (nwritten == -1) { + debug_return_bool(false); } - sudo_debug_enter(__func__, __FILE__, __LINE__, sudo_debug_subsys); + len -= nwritten; + cp += nwritten; + } while (len > 0); - /* - * Missing SUDO_INTERCEPT_FD will result in execve() failure. - * Note that we cannot use getenv(3) here on Linux at least. - */ - for (p = environ; *p != NULL; p++) { - if (strncmp(*p, "SUDO_INTERCEPT_FD=", sizeof("SUDO_INTERCEPT_FD=") -1) == 0) { - const char *fdstr = *p + sizeof("SUDO_INTERCEPT_FD=") - 1; - const char *errstr; - char ch = INTERCEPT_REQ_SEC; - int fd; - - sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "%s", *p); - - fd = sudo_strtonum(fdstr, 0, INT_MAX, &errstr); - if (errstr != NULL) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "invalid SUDO_INTERCEPT_FD: %s: %s", fdstr, errstr); - debug_return; - } - - /* Request secret from parent. */ - if (send(fd, &ch, sizeof(ch), 0) != sizeof(ch)) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "unable to request secret on fd %d: %s", fd, - strerror(errno)); - debug_return; - } - if (recv(fd, &secret, sizeof(secret), 0) != sizeof(secret)) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "unable to read secret on fd %d: %s", fd, - strerror(errno)); - debug_return; - } - - intercept_sock = fd; - debug_return; - } - } - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "SUDO_INTERCEPT_FD not found in environment"); - } - debug_return; + debug_return_bool(true); } -static uint8_t * -fmt_policy_check_req(const char *cmnd, char * const argv[], char * const envp[], - size_t *buflen) +static bool +send_client_hello(int sock) { - InterceptMessage msg = INTERCEPT_MESSAGE__INIT; - PolicyCheckRequest req = POLICY_CHECK_REQUEST__INIT; + InterceptRequest msg = INTERCEPT_REQUEST__INIT; + ClientHello hello = CLIENT_HELLO__INIT; uint8_t *buf = NULL; uint32_t msg_len; size_t len; - debug_decl(sudo_interposer_init, SUDO_DEBUG_EXEC); + bool ret = false; + debug_decl(send_client_hello, SUDO_DEBUG_EXEC); /* Setup policy check request. */ - req.command = (char *)cmnd; - req.argv = (char **)argv; - for (len = 0; argv[len] != NULL; len++) - continue; - req.n_argv = len; - req.envp = (char **)envp; - for (len = 0; envp[len] != NULL; len++) - continue; - req.n_envp = len; - msg.type_case = INTERCEPT_MESSAGE__TYPE_POLICY_CHECK_REQ; - msg.u.policy_check_req = &req; + hello.pid = getpid(); + msg.type_case = INTERCEPT_REQUEST__TYPE_HELLO;; + msg.u.hello = &hello; - len = intercept_message__get_packed_size(&msg); + len = intercept_request__get_packed_size(&msg); if (len > MESSAGE_SIZE_MAX) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "InterceptMessage too large: %zu", len); + "InterceptRequest too large: %zu", len); goto done; } /* Wire message size is used for length encoding, precedes message. */ @@ -164,78 +112,239 @@ fmt_policy_check_req(const char *cmnd, char * const argv[], char * const envp[], goto done; } memcpy(buf, &msg_len, sizeof(msg_len)); - intercept_message__pack(&msg, buf + sizeof(msg_len)); - *buflen = len; + intercept_request__pack(&msg, buf + sizeof(msg_len)); + + ret = send_req(sock, buf, len); done: - debug_return_ptr(buf); + free(buf); + debug_return_bool(ret); } -/* Send fd over a unix domain socket. */ -static bool -intercept_send_fd(int sock, int fd) +/* + * Receive HelloResponse from sudo over fd. + */ +InterceptResponse * +recv_intercept_response(int fd) { - struct msghdr msg; -#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && HAVE_STRUCT_MSGHDR_MSG_CONTROL == 1 - union { - struct cmsghdr hdr; - char buf[CMSG_SPACE(sizeof(int))]; - } cmsgbuf; - struct cmsghdr *cmsg; -#endif - struct iovec iov[1]; - char ch = '\0'; - ssize_t nsent; - debug_decl(intercept_send_fd, SUDO_DEBUG_EXEC); + InterceptResponse *res = NULL; + ssize_t nread; + uint32_t res_len; + uint8_t *buf = NULL; + debug_decl(recv_intercept_response, SUDO_DEBUG_EXEC); + + /* Read message size (uint32_t in host byte order). */ + nread = recv(fd, &res_len, sizeof(res_len), 0); + if ((size_t)nread != sizeof(res_len)) { + if (nread == 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unexpected EOF reading response size"); + } else { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "error reading response size"); + } + goto done; + } + if (res_len > MESSAGE_SIZE_MAX) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "InterceptResponse too large: %u", res_len); + goto done; + } + + /* Read response from sudo (blocking). */ + if ((buf = malloc(res_len)) == NULL) { + goto done; + } + nread = recv(fd, buf, res_len, 0); + if ((size_t)nread != res_len) { + if (nread == 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unexpected EOF reading response"); + } else { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "error reading response"); + } + goto done; + } + res = intercept_response__unpack(NULL, res_len, buf); + if (res == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to unpack %s size %u", "InterceptResponse", res_len); + goto done; + } + +done: + free(buf); + debug_return_ptr(res); +} + +/* + * Look up SUDO_INTERCEPT_FD in the environment. + * This function is run when the shared library is loaded. + */ +__attribute__((constructor)) static void +sudo_interposer_init(void) +{ + InterceptResponse *res = NULL; + static bool initialized; + int fd = -1; + char **p; + debug_decl(sudo_interposer_init, SUDO_DEBUG_EXEC); + + if (initialized) + debug_return; + initialized = true; + + /* Read debug section of sudo.conf and init debugging. */ + if (sudo_conf_read(NULL, SUDO_CONF_DEBUG) != -1) { + sudo_debug_register("sudo_intercept.so", NULL, NULL, + sudo_conf_debug_files("sudo_intercept.so")); + } + sudo_debug_enter(__func__, __FILE__, __LINE__, sudo_debug_subsys); /* - * We send a single byte of data along with the fd; some systems - * don't support sending file descriptors without data. - * Note that the intercept fd is *blocking*. - */ - iov[0].iov_base = &ch; - iov[0].iov_len = 1; - memset(&msg, 0, sizeof(msg)); - msg.msg_iov = iov; - msg.msg_iovlen = 1; -#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && HAVE_STRUCT_MSGHDR_MSG_CONTROL == 1 - memset(&cmsgbuf, 0, sizeof(cmsgbuf)); - msg.msg_control = &cmsgbuf.buf; - msg.msg_controllen = sizeof(cmsgbuf.buf); - cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_len = CMSG_LEN(sizeof(int)); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); -#else - msg.msg_accrights = (caddr_t)&fd; - msg.msg_accrightslen = sizeof(fd); -#endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL */ + * Missing SUDO_INTERCEPT_FD will result in execve() failure. + * Note that we cannot use getenv(3) here on Linux at least. + */ + for (p = environ; *p != NULL; p++) { + if (strncmp(*p, "SUDO_INTERCEPT_FD=", sizeof("SUDO_INTERCEPT_FD=") -1) == 0) { + const char *fdstr = *p + sizeof("SUDO_INTERCEPT_FD=") - 1; + const char *errstr; - for (;;) { - nsent = sendmsg(sock, &msg, 0); - if (nsent != -1) - debug_return_bool(true); - if (errno != EAGAIN && errno != EINTR) - break; + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "%s", *p); + + fd = sudo_strtonum(fdstr, 0, INT_MAX, &errstr); + if (errstr != NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "invalid SUDO_INTERCEPT_FD: %s: %s", fdstr, errstr); + goto done; + } + } } - sudo_warn("sendmsg(%d)", sock); - debug_return_bool(false); + if (fd == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "SUDO_INTERCEPT_FD not found in environment"); + goto done; + } + + /* + * Send ClientHello message to over the fd. + */ + if (!send_client_hello(fd)) + goto done; + + res = recv_intercept_response(fd); + if (res != NULL) { + secret = res->u.hello_resp->secret; + intercept_port = res->u.hello_resp->portno; + intercept_response__free_unpacked(res, NULL); + } + +done: + if (fd != -1) + close(fd); + + debug_return; +} + +static bool +send_policy_check_req(int sock, const char *cmnd, char * const argv[], + char * const envp[]) +{ + InterceptRequest msg = INTERCEPT_REQUEST__INIT; + PolicyCheckRequest req = POLICY_CHECK_REQUEST__INIT; + uint8_t *buf = NULL; + bool ret = false; + uint32_t msg_len; + size_t len; + debug_decl(fmt_policy_check_req, SUDO_DEBUG_EXEC); + + /* Setup policy check request. */ + req.secret = secret; + req.intercept_fd = sock; + req.command = (char *)cmnd; + req.argv = (char **)argv; + for (len = 0; argv[len] != NULL; len++) + continue; + req.n_argv = len; + req.envp = (char **)envp; + for (len = 0; envp[len] != NULL; len++) + continue; + req.n_envp = len; + msg.type_case = INTERCEPT_REQUEST__TYPE_POLICY_CHECK_REQ; + msg.u.policy_check_req = &req; + + len = intercept_request__get_packed_size(&msg); + if (len > MESSAGE_SIZE_MAX) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "InterceptRequest too large: %zu", len); + goto done; + } + /* Wire message size is used for length encoding, precedes message. */ + msg_len = len; + len += sizeof(msg_len); + + if ((buf = malloc(len)) == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + goto done; + } + memcpy(buf, &msg_len, sizeof(msg_len)); + intercept_request__pack(&msg, buf + sizeof(msg_len)); + + ret = send_req(sock, buf, len); + +done: + free(buf); + debug_return_bool(ret); +} + +/* + * Connect back to sudo process at localhost:intercept_port + */ +static int +intercept_connect(void) +{ + int sock = -1; + struct sockaddr_in sin; + debug_decl(command_allowed, SUDO_DEBUG_EXEC); + + if (intercept_port == 0) { + sudo_warnx(U_("intercept port not set")); + goto done; + } + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(intercept_port); + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) { + sudo_warn("socket"); + goto done; + } + + if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) == -1) { + sudo_warn("connect"); + close(sock); + sock = -1; + goto done; + } + +done: + debug_return_int(sock); } bool -command_allowed(const char *cmnd, char * const argv[], char * const envp[], - char **ncmndp, char ***nargvp, char ***nenvpp) +command_allowed(const char *cmnd, char * const argv[], + char * const envp[], char **ncmndp, char ***nargvp, char ***nenvpp) { char *ncmnd = NULL, **nargv = NULL, **nenvp = NULL; - PolicyCheckResult *res = NULL; - int sv[2] = { -1, -1 }; - ssize_t nread, nwritten; - uint8_t *cp, *buf = NULL; + InterceptResponse *res = NULL; bool ret = false; - uint32_t res_len; - size_t idx, len; - debug_decl(intercept_send_fd, SUDO_DEBUG_EXEC); + size_t idx, len = 0; + int sock; + debug_decl(command_allowed, SUDO_DEBUG_EXEC); if (sudo_debug_needed(SUDO_DEBUG_INFO)) { sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, @@ -246,97 +355,19 @@ command_allowed(const char *cmnd, char * const argv[], char * const envp[], } } - if (intercept_sock < INTERCEPT_FD_MIN) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "invalid intercept fd: %d", intercept_sock); - errno = EINVAL; - goto done; - } - if (fcntl(intercept_sock, F_GETFD, 0) == -1) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "intercept fd %d not open", intercept_sock); - errno = EINVAL; - goto done; - } - - /* - * We communicate with the main sudo process over a socket pair - * which is passed over the intercept_sock. The reason for not - * using intercept_sock directly is that multiple processes - * could be trying to use it at once. Sending an fd like this - * is atomic but regular communication is not. - */ - if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) { - sudo_warn("socketpair"); - goto done; - } - if (!intercept_send_fd(intercept_sock, sv[1])) - goto done; - close(sv[1]); - sv[1] = -1; - - buf = fmt_policy_check_req(cmnd, argv, envp, &len); - if (buf == NULL) + sock = intercept_connect(); + if (sock == -1) goto done; - /* Send request to sudo (blocking). */ - cp = buf; - do { - nwritten = write(sv[0], cp, len); - if (nwritten == -1) { - goto done; - } - len -= nwritten; - cp += nwritten; - } while (len > 0); - free(buf); - buf = NULL; - - /* Read message size (uint32_t in host byte order). */ - nread = read(sv[0], &res_len, sizeof(res_len)); - if ((size_t)nread != sizeof(res_len)) { - if (nread == 0) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "unexpected EOF reading result size"); - } else { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, - "error reading result size"); - } - goto done; - } - if (res_len > MESSAGE_SIZE_MAX) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "PolicyCheckResult too large: %zu", len); - goto done; - } - - /* Read result from sudo (blocking). */ - if ((buf = malloc(res_len)) == NULL) { + if (!send_policy_check_req(sock, cmnd, argv, envp)) goto done; - } - nread = read(sv[0], buf, res_len); - if ((size_t)nread != res_len) { - if (nread == 0) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "unexpected EOF reading result"); - } else { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, - "error reading result"); - } - goto done; - } - res = policy_check_result__unpack(NULL, res_len, buf); - if (res == NULL) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "unable to unpack %s size %u", "PolicyCheckResult", res_len); - goto done; - } - if (res->secret != secret) { - sudo_warnx("secret mismatch\r"); + + res = recv_intercept_response(sock); + if (res == NULL) goto done; - } + switch (res->type_case) { - case POLICY_CHECK_RESULT__TYPE_ACCEPT_MSG: + case INTERCEPT_RESPONSE__TYPE_ACCEPT_MSG: if (sudo_debug_needed(SUDO_DEBUG_INFO)) { sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "run_command: %s", res->u.accept_msg->run_command); @@ -360,7 +391,7 @@ command_allowed(const char *cmnd, char * const argv[], char * const envp[], nargv[len] = NULL; // XXX - bogus cast nenvp = sudo_preload_dso((char **)envp, sudo_conf_intercept_path(), - intercept_sock); + sock); if (nenvp == NULL) goto oom; *ncmndp = ncmnd; @@ -368,11 +399,11 @@ command_allowed(const char *cmnd, char * const argv[], char * const envp[], *nenvpp = nenvp; ret = true; goto done; - case POLICY_CHECK_RESULT__TYPE_REJECT_MSG: + case INTERCEPT_RESPONSE__TYPE_REJECT_MSG: /* Policy module displayed reject message but we are in raw mode. */ fputc('\r', stderr); goto done; - case POLICY_CHECK_RESULT__TYPE_ERROR_MSG: + case INTERCEPT_RESPONSE__TYPE_ERROR_MSG: /* Policy module may display error message but we are in raw mode. */ fputc('\r', stderr); sudo_warnx("%s", res->u.error_msg->error_message); @@ -380,7 +411,7 @@ command_allowed(const char *cmnd, char * const argv[], char * const envp[], default: sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, "unexpected type_case value %d in %s from %s", - res->type_case, "PolicyCheckResult", "sudo"); + res->type_case, "InterceptResponse", "sudo"); goto done; } @@ -390,12 +421,10 @@ oom: free(nargv[--len]); done: - policy_check_result__free_unpacked(res, NULL); - if (sv[0] != -1) - close(sv[0]); - if (sv[1] != -1) - close(sv[1]); - free(buf); + /* Keep socket open for ctor when we execute the command. */ + if (!ret && sock != -1) + close(sock); + intercept_response__free_unpacked(res, NULL); debug_return_bool(ret); }