Switch to a 128-bit token instead of a 64-bit secret.

Protobuf doesn't have a 128-bit type so use two u64s.
We now support partial reads of the token.
This commit is contained in:
Todd C. Miller
2021-09-01 10:17:26 -06:00
parent 4bff82cab4
commit 9a690a8984
6 changed files with 84 additions and 46 deletions

View File

@@ -57,7 +57,7 @@ struct _InterceptRequest
/* /*
* Hello message from sudo_intercept.so to main sudo process. * Hello message from sudo_intercept.so to main sudo process.
* Sudo sends back the secret and localhost port number. * Sudo sends back the token and localhost port number.
*/ */
struct _ClientHello struct _ClientHello
{ {
@@ -71,17 +71,18 @@ struct _ClientHello
/* /*
* Sudo response to a ClientHello from sudo_intercept.so. * Sudo response to a ClientHello from sudo_intercept.so.
* The client uses the port number and secret to connect back to sudo. * The client uses the port number and token to connect back to sudo.
*/ */
struct _HelloResponse struct _HelloResponse
{ {
ProtobufCMessage base; ProtobufCMessage base;
uint64_t secret; uint64_t token_lo;
uint64_t token_hi;
int32_t portno; int32_t portno;
}; };
#define HELLO_RESPONSE__INIT \ #define HELLO_RESPONSE__INIT \
{ PROTOBUF_C_MESSAGE_INIT (&hello_response__descriptor) \ { PROTOBUF_C_MESSAGE_INIT (&hello_response__descriptor) \
, 0, 0 } , 0, 0, 0 }
/* /*

View File

@@ -63,6 +63,7 @@ enum intercept_state {
/* Closure for intercept_cb() */ /* Closure for intercept_cb() */
struct intercept_closure { struct intercept_closure {
union sudo_token_un token;
struct command_details *details; struct command_details *details;
struct sudo_event ev; struct sudo_event ev;
const char *errstr; const char *errstr;
@@ -76,7 +77,7 @@ struct intercept_closure {
enum intercept_state state; enum intercept_state state;
}; };
static uint64_t intercept_secret; static union sudo_token_un intercept_token;
static in_port_t intercept_listen_port; static in_port_t intercept_listen_port;
static void intercept_accept_cb(int fd, int what, void *v); static void intercept_accept_cb(int fd, int what, void *v);
static void intercept_cb(int fd, int what, void *v); static void intercept_cb(int fd, int what, void *v);
@@ -98,7 +99,8 @@ intercept_setup(int fd, struct sudo_event_base *evbase,
} }
/* If we've already seen a ClientHello, expect a policy check first. */ /* If we've already seen a ClientHello, expect a policy check first. */
closure->state = intercept_secret ? RECV_SECRET : RECV_HELLO_INITIAL; closure->state = sudo_token_isset(intercept_token) ?
RECV_SECRET : RECV_HELLO_INITIAL;
closure->details = details; closure->details = details;
closure->listen_sock = -1; closure->listen_sock = -1;
@@ -153,7 +155,7 @@ intercept_connection_close(int fd, struct intercept_closure *closure)
/* /*
* Prepare to listen on localhost using an ephemeral port. * Prepare to listen on localhost using an ephemeral port.
* Sets intercept_secret and intercept_listen_port as side effects. * Sets intercept_token and intercept_listen_port as side effects.
*/ */
static bool static bool
prepare_listener(struct intercept_closure *closure) prepare_listener(struct intercept_closure *closure)
@@ -163,10 +165,10 @@ prepare_listener(struct intercept_closure *closure)
int sock; int sock;
debug_decl(prepare_listener, SUDO_DEBUG_EXEC); debug_decl(prepare_listener, SUDO_DEBUG_EXEC);
/* Secret must be non-zero. */ /* Generate a random token. */
do { do {
intercept_secret = arc4random() | ((uint64_t)arc4random() << 32); arc4random_buf(&intercept_token, sizeof(intercept_token));
} while (intercept_secret == 0); } while (!sudo_token_isset(intercept_token));
/* Create localhost listener socket (currently AF_INET only). */ /* Create localhost listener socket (currently AF_INET only). */
sock = socket(AF_INET, SOCK_STREAM, 0); sock = socket(AF_INET, SOCK_STREAM, 0);
@@ -450,33 +452,43 @@ done:
} }
/* /*
* Read shared secret from sudo_intercept.so and verify w/ intercept_secret. * Read token from sudo_intercept.so and verify w/ intercept_token.
* Returns true on success, false on mismatch and -1 on error. * Returns true on success, false on mismatch and -1 on error.
*/ */
static int static int
intercept_verify_secret(int fd) intercept_verify_token(int fd, struct intercept_closure *closure)
{ {
uint64_t secret;
ssize_t nread; ssize_t nread;
debug_decl(intercept_read_secret, SUDO_DEBUG_EXEC); debug_decl(intercept_read_token, SUDO_DEBUG_EXEC);
/* Read shared secret (uint64_t in host byte order). */ nread = recv(fd, &closure->token + closure->off,
nread = recv(fd, &secret, sizeof(secret), 0); sizeof(closure->token) - closure->off, 0);
if (nread != sizeof(secret)) { switch (nread) {
if (nread == -1) case 0:
debug_return_int(-1);
/* Treat short read as a secret mismatch. */
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"short read, expected %zu, got %zd", sizeof(secret), nread); "EOF reading token");
debug_return_int(false);
}
if (secret != intercept_secret) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"secret mismatch: got %" PRIu64 ", expected %" PRIu64, secret,
intercept_secret);
debug_return_int(false); debug_return_int(false);
case -1:
debug_return_int(-1);
default:
if (nread + closure->off == sizeof(closure->token))
break;
/* partial read, update offset and try again */
closure->off += nread;
errno = EAGAIN;
debug_return_int(-1);
} }
closure->off = 0;
if (memcmp(&closure->token, &intercept_token, sizeof(closure->token)) != 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"token mismatch: got 0x%8x%8x%8x%8x, expected 0x%8x%8x%8x%8x",
closure->token.u32[3], closure->token.u32[2],
closure->token.u32[1], closure->token.u32[0],
intercept_token.u32[3], intercept_token.u32[2],
intercept_token.u32[1], intercept_token.u32[0]);
debug_return_int(false);
}
debug_return_int(true); debug_return_int(true);
} }
@@ -496,7 +508,7 @@ intercept_read(int fd, struct intercept_closure *closure)
debug_decl(intercept_read, SUDO_DEBUG_EXEC); debug_decl(intercept_read, SUDO_DEBUG_EXEC);
if (closure->state == RECV_SECRET) { if (closure->state == RECV_SECRET) {
switch (intercept_verify_secret(fd)) { switch (intercept_verify_token(fd, closure)) {
case true: case true:
closure->state = RECV_POLICY_CHECK; closure->state = RECV_POLICY_CHECK;
break; break;
@@ -505,6 +517,7 @@ intercept_read(int fd, struct intercept_closure *closure)
default: default:
if (errno == EINTR || errno == EAGAIN) if (errno == EINTR || errno == EAGAIN)
debug_return_bool(true); debug_return_bool(true);
sudo_warn("recv");
goto done; goto done;
} }
} }
@@ -693,7 +706,8 @@ fmt_hello_response(struct intercept_closure *closure)
debug_decl(fmt_hello_response, SUDO_DEBUG_EXEC); debug_decl(fmt_hello_response, SUDO_DEBUG_EXEC);
hello_resp.portno = intercept_listen_port; hello_resp.portno = intercept_listen_port;
hello_resp.secret = intercept_secret; hello_resp.token_lo = intercept_token.u64[0];
hello_resp.token_hi = intercept_token.u64[1];
resp.u.hello_resp = &hello_resp; resp.u.hello_resp = &hello_resp;
resp.type_case = INTERCEPT_RESPONSE__TYPE_HELLO_RESP; resp.type_case = INTERCEPT_RESPONSE__TYPE_HELLO_RESP;

View File

@@ -456,15 +456,27 @@ const ProtobufCMessageDescriptor client_hello__descriptor =
(ProtobufCMessageInit) client_hello__init, (ProtobufCMessageInit) client_hello__init,
NULL,NULL,NULL /* reserved[123] */ NULL,NULL,NULL /* reserved[123] */
}; };
static const ProtobufCFieldDescriptor hello_response__field_descriptors[2] = static const ProtobufCFieldDescriptor hello_response__field_descriptors[3] =
{ {
{ {
"secret", "token_lo",
1, 1,
PROTOBUF_C_LABEL_NONE, PROTOBUF_C_LABEL_NONE,
PROTOBUF_C_TYPE_FIXED64, PROTOBUF_C_TYPE_FIXED64,
0, /* quantifier_offset */ 0, /* quantifier_offset */
offsetof(HelloResponse, secret), offsetof(HelloResponse, token_lo),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
{
"token_hi",
2,
PROTOBUF_C_LABEL_NONE,
PROTOBUF_C_TYPE_FIXED64,
0, /* quantifier_offset */
offsetof(HelloResponse, token_hi),
NULL, NULL,
NULL, NULL,
0, /* flags */ 0, /* flags */
@@ -472,7 +484,7 @@ static const ProtobufCFieldDescriptor hello_response__field_descriptors[2] =
}, },
{ {
"portno", "portno",
2, 3,
PROTOBUF_C_LABEL_NONE, PROTOBUF_C_LABEL_NONE,
PROTOBUF_C_TYPE_INT32, PROTOBUF_C_TYPE_INT32,
0, /* quantifier_offset */ 0, /* quantifier_offset */
@@ -484,13 +496,14 @@ static const ProtobufCFieldDescriptor hello_response__field_descriptors[2] =
}, },
}; };
static const unsigned hello_response__field_indices_by_name[] = { static const unsigned hello_response__field_indices_by_name[] = {
1, /* field[1] = portno */ 2, /* field[2] = portno */
0, /* field[0] = secret */ 1, /* field[1] = token_hi */
0, /* field[0] = token_lo */
}; };
static const ProtobufCIntRange hello_response__number_ranges[1 + 1] = static const ProtobufCIntRange hello_response__number_ranges[1 + 1] =
{ {
{ 1, 0 }, { 1, 0 },
{ 0, 2 } { 0, 3 }
}; };
const ProtobufCMessageDescriptor hello_response__descriptor = const ProtobufCMessageDescriptor hello_response__descriptor =
{ {
@@ -500,7 +513,7 @@ const ProtobufCMessageDescriptor hello_response__descriptor =
"HelloResponse", "HelloResponse",
"", "",
sizeof(HelloResponse), sizeof(HelloResponse),
2, 3,
hello_response__field_descriptors, hello_response__field_descriptors,
hello_response__field_indices_by_name, hello_response__field_indices_by_name,
1, hello_response__number_ranges, 1, hello_response__number_ranges,

View File

@@ -13,7 +13,7 @@ message InterceptRequest {
/* /*
* Hello message from sudo_intercept.so to main sudo process. * Hello message from sudo_intercept.so to main sudo process.
* Sudo sends back the secret and localhost port number. * Sudo sends back the token and localhost port number.
*/ */
message ClientHello { message ClientHello {
int32 pid = 1; int32 pid = 1;
@@ -21,11 +21,12 @@ message ClientHello {
/* /*
* Sudo response to a ClientHello from sudo_intercept.so. * Sudo response to a ClientHello from sudo_intercept.so.
* The client uses the port number and secret to connect back to sudo. * The client uses the port number and token to connect back to sudo.
*/ */
message HelloResponse { message HelloResponse {
fixed64 secret = 1; fixed64 token_lo = 1;
int32 portno = 2; fixed64 token_hi = 2;
int32 portno = 3;
} }
/* /*

View File

@@ -82,6 +82,14 @@
#define INTERCEPT_FD_MIN 64 /* minimum fd so shell won't close it */ #define INTERCEPT_FD_MIN 64 /* minimum fd so shell won't close it */
#define MESSAGE_SIZE_MAX 2097152 /* 2Mib max intercept message size */ #define MESSAGE_SIZE_MAX 2097152 /* 2Mib max intercept message size */
union sudo_token_un {
unsigned char u8[16];
unsigned int u32[4];
unsigned long long u64[2];
};
#define sudo_token_isset(_t) ((_t).u64[0] || (_t).u64[1])
/* /*
* Symbols shared between exec.c, exec_nopty.c, exec_pty.c and exec_monitor.c * Symbols shared between exec.c, exec_nopty.c, exec_pty.c and exec_monitor.c
*/ */

View File

@@ -58,7 +58,7 @@
extern char **environ; extern char **environ;
static uint64_t secret; static union sudo_token_un intercept_token;
static in_port_t intercept_port; static in_port_t intercept_port;
/* Send entire request to sudo (blocking). */ /* Send entire request to sudo (blocking). */
@@ -247,7 +247,8 @@ sudo_interposer_init(void)
res = recv_intercept_response(fd); res = recv_intercept_response(fd);
if (res != NULL) { if (res != NULL) {
secret = res->u.hello_resp->secret; intercept_token.u64[0] = res->u.hello_resp->token_lo;
intercept_token.u64[1] = res->u.hello_resp->token_hi;
intercept_port = res->u.hello_resp->portno; intercept_port = res->u.hello_resp->portno;
intercept_response__free_unpacked(res, NULL); intercept_response__free_unpacked(res, NULL);
} }
@@ -272,10 +273,10 @@ send_policy_check_req(int sock, const char *cmnd, char * const argv[],
size_t len; size_t len;
debug_decl(fmt_policy_check_req, SUDO_DEBUG_EXEC); debug_decl(fmt_policy_check_req, SUDO_DEBUG_EXEC);
/* Send secret first (out of band) to initiate connection. */ /* Send token first (out of band) to initiate connection. */
if (!send_req(sock, &secret, sizeof(secret))) { if (!send_req(sock, &intercept_token, sizeof(intercept_token))) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"unable to send secret back to sudo"); "unable to send token back to sudo");
goto done; goto done;
} }