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:
@@ -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 }
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@@ -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;
|
||||||
|
@@ -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,
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@@ -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
|
||||||
*/
|
*/
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user