mirror of
https://github.com/shadowsocks/shadowsocks-libev.git
synced 2026-10-28 19:53:46 +00:00
C++20 port 9/14: own AEAD-2022 UDP sessions by reference (eviction fix) (#3073)
* Own AEAD-2022 UDP sessions by reference so eviction cannot free a live session The session table and the relay contexts each hold a reference; evicting a session from the full table no longer frees memory a relay still uses. Ported from the parked fix/aead2022-udp-session-eviction work. * Include <netinet/in.h> in the UDP eviction test client (FreeBSD) FreeBSD's <arpa/inet.h> does not define struct sockaddr_in.
This commit is contained in:
@@ -52,6 +52,13 @@ cipher_t *aead_init(const char *pass, const char *key, const char *method);
|
||||
int aead_is_2022(const cipher_t *cipher);
|
||||
void aead_set_role(int role); /* override compile-time default; used by tests */
|
||||
|
||||
/*
|
||||
* Server-side UDP session table size. Inserting beyond it evicts the least
|
||||
* recently used session; a relay context that still references the evicted
|
||||
* session keeps it alive until it releases it.
|
||||
*/
|
||||
#define AEAD_2022_UDP_SESSION_TABLE_CAP 4096
|
||||
|
||||
int aead_2022_encrypt_udp(buffer_t *, cipher_t *, size_t, void **);
|
||||
int aead_2022_decrypt_udp(buffer_t *, cipher_t *, size_t, void **);
|
||||
void aead_2022_udp_session_release(void *);
|
||||
|
||||
+24
-21
@@ -41,6 +41,7 @@
|
||||
#include "aead_internal.h"
|
||||
#include "cache.h"
|
||||
#include "utils.h"
|
||||
#include "ss_ref.h"
|
||||
|
||||
#define SS2022_UDP_TYPE_CLIENT 0
|
||||
#define SS2022_UDP_TYPE_SERVER 1
|
||||
@@ -59,6 +60,7 @@
|
||||
* peer session we are receiving from.
|
||||
*/
|
||||
typedef struct ss2022_udp_session {
|
||||
ss_ref_t ref; /* Table and relay slots independently own this session. */
|
||||
cipher_t *cipher;
|
||||
|
||||
/* outgoing */
|
||||
@@ -78,12 +80,6 @@ typedef struct ss2022_udp_session {
|
||||
uint8_t peer_session_id[SS2022_SESSION_ID_LEN];
|
||||
int peer_session_known;
|
||||
|
||||
/*
|
||||
* Server sessions are owned by the session table (they are looked up by
|
||||
* client session ID, which is only known after decryption); client
|
||||
* sessions are owned by the caller's slot.
|
||||
*/
|
||||
int table_owned;
|
||||
} ss2022_udp_session_t;
|
||||
|
||||
/*
|
||||
@@ -91,19 +87,24 @@ typedef struct ss2022_udp_session {
|
||||
* requires servers to route by session ID rather than source address, so
|
||||
* that sessions survive client network changes.
|
||||
*/
|
||||
#define SS2022_SESSION_TABLE_CAP 4096
|
||||
#define SS2022_SESSION_TABLE_CAP AEAD_2022_UDP_SESSION_TABLE_CAP
|
||||
#define SS2022_SESSION_TTL 600
|
||||
|
||||
static struct cache *ss2022_sessions = NULL;
|
||||
|
||||
static void
|
||||
session_destroy(void *object)
|
||||
{
|
||||
sodium_memzero(object, sizeof(ss2022_udp_session_t));
|
||||
ss_free(object);
|
||||
}
|
||||
|
||||
/* Cache callback: drop the table's reference when an entry is evicted. */
|
||||
static void
|
||||
session_free_cb(void *key, void *element)
|
||||
{
|
||||
(void)key;
|
||||
if (element != NULL) {
|
||||
sodium_memzero(element, sizeof(ss2022_udp_session_t));
|
||||
ss_free(element);
|
||||
}
|
||||
ss_ref_release(element);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -181,7 +182,8 @@ session_new(cipher_t *cipher)
|
||||
{
|
||||
ss2022_udp_session_t *s = (ss2022_udp_session_t *)ss_malloc(sizeof(ss2022_udp_session_t));
|
||||
memset(s, 0, sizeof(ss2022_udp_session_t));
|
||||
s->cipher = cipher;
|
||||
s->cipher = cipher;
|
||||
ss_ref_init(s, session_destroy);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -214,20 +216,20 @@ session_by_id(const uint8_t *session_id, cipher_t *cipher)
|
||||
|
||||
cache_clear(ss2022_sessions, SS2022_SESSION_TTL);
|
||||
|
||||
s = session_new(cipher);
|
||||
s->table_owned = 1;
|
||||
cache_insert(ss2022_sessions, (char *)session_id, SS2022_SESSION_ID_LEN, s);
|
||||
s = session_new(cipher);
|
||||
/* Transfer the initial reference to the table on successful insertion. */
|
||||
if (cache_insert(ss2022_sessions, (char *)session_id,
|
||||
SS2022_SESSION_ID_LEN, s) != 0) {
|
||||
ss_ref_release(s);
|
||||
return NULL;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void
|
||||
aead_2022_udp_session_release(void *slot)
|
||||
{
|
||||
ss2022_udp_session_t *s = (ss2022_udp_session_t *)slot;
|
||||
if (s == NULL || s->table_owned)
|
||||
return; /* table-owned sessions are freed when the table evicts them */
|
||||
sodium_memzero(s, sizeof(ss2022_udp_session_t));
|
||||
ss_free(s);
|
||||
ss_ref_release(slot);
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -531,7 +533,6 @@ aead_2022_decrypt_udp(buffer_t *ciphertext, cipher_t *cipher, size_t capacity,
|
||||
s = session_by_id(session_id, cipher);
|
||||
if (s == NULL)
|
||||
return CRYPTO_ERROR;
|
||||
*slot = s; /* the reply path encrypts with this same session */
|
||||
} else {
|
||||
s = session_get(slot, cipher);
|
||||
}
|
||||
@@ -585,6 +586,8 @@ aead_2022_decrypt_udp(buffer_t *ciphertext, cipher_t *cipher, size_t capacity,
|
||||
if (is_server) {
|
||||
memcpy(s->peer_session_id, session_id, SS2022_SESSION_ID_LEN);
|
||||
s->peer_session_known = 1;
|
||||
/* Publish only validated packets and retain the reply session. */
|
||||
ss_ref_assign(slot, s);
|
||||
}
|
||||
|
||||
size_t payload_len = plen - o;
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#ifndef SS_REF_H
|
||||
#define SS_REF_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Intrusive shared ownership for a single event-loop thread. Embed this as
|
||||
* the FIRST member of an object, initialize once, and never copy the object.
|
||||
* Each owning slot/table entry holds one reference; raw aliases are borrowed.
|
||||
* The destructor frees the enclosing object. Cycles and cross-thread sharing
|
||||
* require a different ownership scheme. */
|
||||
typedef struct ss_ref {
|
||||
size_t count;
|
||||
void (*destroy)(void *);
|
||||
} ss_ref_t;
|
||||
|
||||
static inline void
|
||||
ss_ref_init(void *object, void (*destroy)(void *))
|
||||
{
|
||||
ss_ref_t *ref = (ss_ref_t *)object;
|
||||
ref->count = 1;
|
||||
ref->destroy = destroy;
|
||||
}
|
||||
|
||||
static inline void *
|
||||
ss_ref_retain(void *object)
|
||||
{
|
||||
if (object != NULL) {
|
||||
ss_ref_t *ref = (ss_ref_t *)object;
|
||||
if (ref->count == 0 || ref->count == SIZE_MAX)
|
||||
abort();
|
||||
ref->count++;
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
static inline void
|
||||
ss_ref_release(void *object)
|
||||
{
|
||||
if (object != NULL) {
|
||||
ss_ref_t *ref = (ss_ref_t *)object;
|
||||
if (ref->count == 0)
|
||||
abort();
|
||||
if (--ref->count == 0)
|
||||
ref->destroy(object);
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset/move also support opaque objects with their own release callback.
|
||||
* Slots must be actual void * objects, not casts from typed pointer slots.
|
||||
* Move consumes the source reference and releases the previous destination. */
|
||||
static inline void
|
||||
ss_ptr_reset(void **slot, void (*release)(void *))
|
||||
{
|
||||
void *old = *slot;
|
||||
*slot = NULL;
|
||||
if (old != NULL)
|
||||
release(old);
|
||||
}
|
||||
|
||||
static inline void
|
||||
ss_ptr_move(void **dst, void **src, void (*release)(void *))
|
||||
{
|
||||
if (dst == src)
|
||||
return;
|
||||
void *old = *dst;
|
||||
*dst = *src;
|
||||
*src = NULL;
|
||||
if (old != NULL)
|
||||
release(old);
|
||||
}
|
||||
|
||||
/* Copy a borrowed reference into an owning slot, retaining before release. */
|
||||
static inline void
|
||||
ss_ref_assign(void **slot, void *object)
|
||||
{
|
||||
void *owned = ss_ref_retain(object);
|
||||
ss_ptr_move(slot, &owned, ss_ref_release);
|
||||
}
|
||||
|
||||
#endif /* SS_REF_H */
|
||||
+17
-10
@@ -52,6 +52,7 @@
|
||||
#include "netutils.h"
|
||||
#include "cache.h"
|
||||
#include "udprelay.h"
|
||||
#include "ss_ref.h"
|
||||
#include "ss_windows.h"
|
||||
|
||||
#ifdef MODULE_REMOTE
|
||||
@@ -628,6 +629,7 @@ close_and_free_query(SS_P_ struct query_ctx *ctx)
|
||||
bfree(ctx->buf);
|
||||
ss_free(ctx->buf);
|
||||
}
|
||||
ss_ptr_reset(&ctx->udp_session, ctx->server_ctx->crypto->udp_session_release);
|
||||
ss_free(ctx);
|
||||
}
|
||||
}
|
||||
@@ -641,11 +643,8 @@ close_and_free_remote(SS_P_ remote_ctx_t *ctx)
|
||||
ss_timer_stop(SS_A_ & ctx->watcher);
|
||||
ss_io_stop(SS_A_ & ctx->io);
|
||||
ss_socket_close(ctx->fd);
|
||||
if (ctx->udp_session != NULL
|
||||
&& ctx->server_ctx != NULL
|
||||
&& ctx->server_ctx->crypto->udp_session_release != NULL) {
|
||||
ctx->server_ctx->crypto->udp_session_release(ctx->udp_session);
|
||||
}
|
||||
if (ctx->server_ctx != NULL)
|
||||
ss_ptr_reset(&ctx->udp_session, ctx->server_ctx->crypto->udp_session_release);
|
||||
ss_free(ctx);
|
||||
}
|
||||
}
|
||||
@@ -735,6 +734,8 @@ resolv_cb(struct sockaddr *addr, void *data)
|
||||
}
|
||||
|
||||
if (remote_ctx != NULL) {
|
||||
ss_ptr_move(&remote_ctx->udp_session, &query_ctx->udp_session,
|
||||
query_ctx->server_ctx->crypto->udp_session_release);
|
||||
if (addr->sa_family == AF_INET)
|
||||
memcpy(&remote_ctx->dst_addr, addr, sizeof(struct sockaddr_in));
|
||||
else
|
||||
@@ -1018,6 +1019,9 @@ server_recv_cb(SS_P_ ss_io *w, int revents)
|
||||
|
||||
socklen_t src_addr_len = sizeof(struct sockaddr_storage);
|
||||
unsigned int offset = 0;
|
||||
#ifdef MODULE_REMOTE
|
||||
void *udp_session = NULL;
|
||||
#endif
|
||||
|
||||
#ifdef MODULE_REDIR
|
||||
char control_buffer[64] = { 0 };
|
||||
@@ -1083,8 +1087,6 @@ server_recv_cb(SS_P_ ss_io *w, int revents)
|
||||
* decryption, so the crypto layer resolves the session itself and hands
|
||||
* it back here to be attached to the relay context.
|
||||
*/
|
||||
void *udp_session;
|
||||
udp_session = NULL;
|
||||
int err;
|
||||
err = server_ctx->crypto->decrypt_udp != NULL
|
||||
? server_ctx->crypto->decrypt_udp(buf, server_ctx->crypto->cipher,
|
||||
@@ -1475,9 +1477,9 @@ server_recv_cb(SS_P_ ss_io *w, int revents)
|
||||
}
|
||||
}
|
||||
|
||||
if (remote_ctx != NULL) {
|
||||
// Attach the AEAD-2022 session so replies are encrypted with it
|
||||
remote_ctx->udp_session = udp_session;
|
||||
if (remote_ctx != NULL && !need_query) {
|
||||
ss_ptr_move(&remote_ctx->udp_session, &udp_session,
|
||||
server_ctx->crypto->udp_session_release);
|
||||
}
|
||||
|
||||
if (remote_ctx != NULL && !need_query) {
|
||||
@@ -1506,6 +1508,8 @@ server_recv_cb(SS_P_ ss_io *w, int revents)
|
||||
struct query_ctx *query_ctx = new_query_ctx(buf->data + addr_header_len,
|
||||
buf->len - addr_header_len);
|
||||
query_ctx->server_ctx = server_ctx;
|
||||
ss_ptr_move(&query_ctx->udp_session, &udp_session,
|
||||
server_ctx->crypto->udp_session_release);
|
||||
query_ctx->addr_header_len = addr_header_len;
|
||||
query_ctx->src_addr = src_addr;
|
||||
memcpy(query_ctx->addr_header, addr_header, addr_header_len);
|
||||
@@ -1525,6 +1529,9 @@ server_recv_cb(SS_P_ ss_io *w, int revents)
|
||||
#endif
|
||||
|
||||
CLEAN_UP:
|
||||
#ifdef MODULE_REMOTE
|
||||
ss_ptr_reset(&udp_session, server_ctx->crypto->udp_session_release);
|
||||
#endif
|
||||
bfree(buf);
|
||||
ss_free(buf);
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ typedef struct query_ctx {
|
||||
char addr_header[MAX_ADDR_HEADER_SIZE];
|
||||
struct server_ctx *server_ctx;
|
||||
struct remote_ctx *remote_ctx;
|
||||
void *udp_session; /* Owned until transferred to the resolved relay. */
|
||||
} query_ctx_t;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -95,6 +95,9 @@ if(Python3_Interpreter_FOUND)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Shared-reference ownership and consuming pointer moves.
|
||||
ss_add_test(test_ref "test_ref.c" "")
|
||||
|
||||
# test_base64 - no external deps
|
||||
ss_add_test(test_base64
|
||||
"test_base64.c;${PROJECT_SOURCE_DIR}/src/base64.cpp"
|
||||
@@ -290,6 +293,18 @@ if(SS_BUILD_EXECUTABLES AND NOT CMAKE_CROSSCOMPILING)
|
||||
--bin-dir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
set_tests_properties(test_cli PROPERTIES LABELS "integration" TIMEOUT 60)
|
||||
if(UNIX)
|
||||
add_executable(udp_session_eviction_client
|
||||
udp_session_eviction_client.c ${SS_CRYPTO_TEST_SOURCES})
|
||||
target_compile_definitions(udp_session_eviction_client PRIVATE HAVE_CONFIG_H)
|
||||
target_compile_options(udp_session_eviction_client PRIVATE -UNDEBUG)
|
||||
target_link_libraries(udp_session_eviction_client
|
||||
${TEST_CRYPTO_LIBS} ${SS_MATH_LIBRARY})
|
||||
ss_project_options(udp_session_eviction_client)
|
||||
add_test(NAME test_udp_session_eviction
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_udp_session_eviction.py
|
||||
--bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
set_tests_properties(test_udp_session_eviction PROPERTIES
|
||||
LABELS "integration" TIMEOUT 300)
|
||||
add_test(NAME test_experiment_supervisor
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/experiments/test_supervisor.py
|
||||
--bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
@@ -327,6 +327,10 @@ test_udp_replay_rejected(const char *method)
|
||||
|
||||
aead_set_role(CRYPTO_ROLE_SERVER);
|
||||
assert(c->decrypt_udp(&buf, c->cipher, 2048, &server_session) == CRYPTO_OK);
|
||||
/* A rejected packet must not publish a session into a fresh relay slot. */
|
||||
void *rejected_session = NULL;
|
||||
assert(c->decrypt_udp(©, c->cipher, 2048, &rejected_session) == CRYPTO_ERROR);
|
||||
assert(rejected_session == NULL);
|
||||
/* the very same packet again must be refused */
|
||||
assert(c->decrypt_udp(©, c->cipher, 2048, &server_session) == CRYPTO_ERROR);
|
||||
|
||||
@@ -338,6 +342,83 @@ test_udp_replay_rejected(const char *method)
|
||||
free_crypto(c);
|
||||
}
|
||||
|
||||
/* Encrypt one client packet into buf and hand it to the server's slot. */
|
||||
static void
|
||||
client_to_server(crypto_t *c, buffer_t *buf, const uint8_t *pkt, size_t len,
|
||||
void **client_session, void **server_session)
|
||||
{
|
||||
memcpy(buf->data, pkt, len);
|
||||
buf->len = len;
|
||||
aead_set_role(CRYPTO_ROLE_CLIENT);
|
||||
assert(c->encrypt_udp(buf, c->cipher, 2048, client_session) == CRYPTO_OK);
|
||||
aead_set_role(CRYPTO_ROLE_SERVER);
|
||||
assert(c->decrypt_udp(buf, c->cipher, 2048, server_session) == CRYPTO_OK);
|
||||
assert(buf->len == len && memcmp(buf->data, pkt, len) == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* A relay context keeps its server session across table eviction. Flooding
|
||||
* the table with fresh client session IDs used to free the session behind
|
||||
* the relay's back, so the reply path encrypted with freed memory.
|
||||
*/
|
||||
static void
|
||||
test_udp_session_survives_table_eviction(const char *method)
|
||||
{
|
||||
static const uint8_t pkt[] = { 0x01, 8, 8, 8, 8, 0x00, 0x35, 0x42 };
|
||||
|
||||
crypto_t *c = make_crypto(method);
|
||||
void *client_session = NULL;
|
||||
void *server_session = NULL;
|
||||
|
||||
buffer_t buf;
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
balloc(&buf, 2048);
|
||||
|
||||
client_to_server(c, &buf, pkt, sizeof(pkt), &client_session, &server_session);
|
||||
assert(server_session != NULL);
|
||||
void *first = server_session;
|
||||
void *shared_session = NULL;
|
||||
client_to_server(c, &buf, pkt, sizeof(pkt), &client_session, &shared_session);
|
||||
assert(shared_session == first);
|
||||
/* Reattaching the same slot must not acquire an extra reference. */
|
||||
client_to_server(c, &buf, pkt, sizeof(pkt), &client_session, &server_session);
|
||||
assert(server_session == first);
|
||||
|
||||
/* Each new client session ID inserts a table entry; overflow evicts ours. */
|
||||
for (int i = 0; i < AEAD_2022_UDP_SESSION_TABLE_CAP + 1; i++) {
|
||||
void *other_client = NULL, *other_server = NULL;
|
||||
client_to_server(c, &buf, pkt, sizeof(pkt), &other_client, &other_server);
|
||||
assert(other_server != NULL && other_server != first);
|
||||
c->udp_session_release(other_client);
|
||||
c->udp_session_release(other_server);
|
||||
}
|
||||
|
||||
/* Dropping one relay owner must leave the other one usable. */
|
||||
c->udp_session_release(shared_session);
|
||||
|
||||
/* The reply must still encrypt with the evicted session and verify. */
|
||||
memcpy(buf.data, pkt, sizeof(pkt));
|
||||
buf.len = sizeof(pkt);
|
||||
aead_set_role(CRYPTO_ROLE_SERVER);
|
||||
assert(c->encrypt_udp(&buf, c->cipher, 2048, &server_session) == CRYPTO_OK);
|
||||
assert(server_session == first);
|
||||
aead_set_role(CRYPTO_ROLE_CLIENT);
|
||||
assert(c->decrypt_udp(&buf, c->cipher, 2048, &client_session) == CRYPTO_OK);
|
||||
assert(buf.len == sizeof(pkt) && memcmp(buf.data, pkt, sizeof(pkt)) == 0);
|
||||
|
||||
/* The client's next packet re-creates a table entry and swaps the slot. */
|
||||
client_to_server(c, &buf, pkt, sizeof(pkt), &client_session, &server_session);
|
||||
assert(server_session != NULL && server_session != first);
|
||||
|
||||
/* Releasing the slot after eviction must neither leak nor double free. */
|
||||
c->udp_session_release(server_session);
|
||||
c->udp_session_release(client_session);
|
||||
|
||||
bfree(&buf);
|
||||
aead_set_role(CRYPTO_ROLE_CLIENT);
|
||||
free_crypto(c);
|
||||
}
|
||||
|
||||
/* The 2022 ciphers must reject a PSK of the wrong size rather than pad it. */
|
||||
static void
|
||||
test_psk_parsing(void)
|
||||
@@ -386,6 +467,7 @@ main(void)
|
||||
test_tcp_tamper_detection(m);
|
||||
test_udp_roundtrip(m);
|
||||
test_udp_replay_rejected(m);
|
||||
test_udp_session_survives_table_eviction(m);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "ss_ref.h"
|
||||
|
||||
typedef struct {
|
||||
ss_ref_t ref;
|
||||
int *destroyed;
|
||||
} test_object_t;
|
||||
|
||||
static void
|
||||
destroy_object(void *object)
|
||||
{
|
||||
test_object_t *value = object;
|
||||
++*value->destroyed;
|
||||
free(value);
|
||||
}
|
||||
|
||||
static void *
|
||||
new_object(int *destroyed)
|
||||
{
|
||||
test_object_t *value = malloc(sizeof(*value));
|
||||
assert(value != NULL);
|
||||
ss_ref_init(value, destroy_object);
|
||||
value->destroyed = destroyed;
|
||||
return value;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
int destroyed = 0;
|
||||
void *table = new_object(&destroyed);
|
||||
void *relay = NULL;
|
||||
ss_ref_assign(&relay, table);
|
||||
ss_ref_assign(&relay, relay); /* Self-assignment must preserve ownership. */
|
||||
ss_ptr_reset(&table, ss_ref_release); /* Simulate eviction. */
|
||||
assert(table == NULL && destroyed == 0);
|
||||
ss_ptr_reset(&relay, ss_ref_release);
|
||||
assert(relay == NULL && destroyed == 1);
|
||||
ss_ptr_reset(&relay, ss_ref_release); /* Repeated cleanup is harmless. */
|
||||
assert(destroyed == 1);
|
||||
|
||||
void *query = new_object(&destroyed);
|
||||
relay = new_object(&destroyed);
|
||||
ss_ptr_move(&relay, &query, ss_ref_release);
|
||||
assert(query == NULL && relay != NULL && destroyed == 2);
|
||||
ss_ptr_move(&relay, &relay, ss_ref_release); /* Self-move. */
|
||||
assert(relay != NULL && destroyed == 2);
|
||||
ss_ref_assign(&query, relay);
|
||||
ss_ptr_move(&relay, &query, ss_ref_release); /* Two owners, same object. */
|
||||
assert(query == NULL && relay != NULL && destroyed == 2);
|
||||
ss_ref_assign(&relay, NULL);
|
||||
assert(relay == NULL && destroyed == 3);
|
||||
|
||||
relay = new_object(&destroyed);
|
||||
ss_ptr_move(&relay, &query, ss_ref_release); /* Move an empty source. */
|
||||
assert(relay == NULL && query == NULL && destroyed == 4);
|
||||
assert(ss_ref_retain(NULL) == NULL);
|
||||
ss_ref_release(NULL);
|
||||
ss_ptr_reset(&relay, NULL); /* Legacy crypto has no session/callback. */
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Exercise the production relay at the exact AEAD-2022 cache boundary."""
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
from interop import free_port, peers
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--bin', type=Path, required=True)
|
||||
parser.add_argument('--server', type=Path)
|
||||
parser.add_argument('--count', type=int, choices=(4095, 4096))
|
||||
args = parser.parse_args()
|
||||
binaries = args.bin.resolve()
|
||||
server = (args.server or binaries / 'ss-server').resolve()
|
||||
environment = dict(os.environ, UBSAN_OPTIONS='halt_on_error=1:print_stacktrace=1')
|
||||
for method in ('2022-blake3-aes-128-gcm', '2022-blake3-aes-256-gcm',
|
||||
'2022-blake3-chacha20-poly1305'):
|
||||
key = ('AAAAAAAAAAAAAAAAAAAAAA==' if 'aes-128' in method else
|
||||
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=')
|
||||
for count in ((args.count,) if args.count else (4095, 4096)):
|
||||
port = free_port()
|
||||
command = [str(server), '-s', '127.0.0.1', '-p', str(port),
|
||||
'-k', key, '-m', method, '-u', '-t', '60']
|
||||
with peers([command], [port], environment):
|
||||
subprocess.run([str(binaries / 'udp_session_eviction_client'),
|
||||
str(port), str(count), method, key],
|
||||
env=environment, check=True, timeout=45)
|
||||
print(f'{method}: {count} sessions, both replies verified', flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,114 @@
|
||||
/* Live UDP relay regression based on Akhil Koul (@akoul)'s private report.
|
||||
* Two source sockets preserve one relay while replacing the other's session.
|
||||
* Receive each forwarded packet before sending the next: no racing counters,
|
||||
* sleeps, or unbounded waits are needed to hit the exact cache boundary. */
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sodium.h>
|
||||
#include "aead.h"
|
||||
#include "crypto.h"
|
||||
#include "ppbloom.h"
|
||||
#include "utils.h"
|
||||
|
||||
int verbose = 0;
|
||||
struct cache *nonce_cache = NULL;
|
||||
|
||||
static int
|
||||
udp_socket(void)
|
||||
{
|
||||
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
assert(fd >= 0);
|
||||
struct timeval timeout = { 5, 0 };
|
||||
assert(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == 0);
|
||||
return fd;
|
||||
}
|
||||
|
||||
static void
|
||||
check_reply(int upstream, int client, const struct sockaddr_in *peer,
|
||||
crypto_t *crypto, void **session, buffer_t *packet)
|
||||
{
|
||||
const unsigned char reply = 0x42;
|
||||
assert(sendto(upstream, &reply, 1, 0, (const struct sockaddr *)peer,
|
||||
sizeof(*peer)) == 1);
|
||||
ssize_t n = recv(client, packet->data, packet->capacity, 0);
|
||||
assert(n > 0);
|
||||
packet->len = (size_t)n;
|
||||
assert(crypto->decrypt_udp(packet, crypto->cipher, 2048, session) == CRYPTO_OK);
|
||||
assert(packet->len == 8 && (unsigned char)packet->data[0] == 1);
|
||||
assert((unsigned char)packet->data[7] == reply);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
assert(argc == 5);
|
||||
int port = atoi(argv[1]);
|
||||
int count = atoi(argv[2]);
|
||||
assert(port > 0 && port <= 65535 && count > 1);
|
||||
assert(sodium_init() >= 0);
|
||||
crypto_t *crypto = crypto_init(argv[4], NULL, argv[3]);
|
||||
assert(crypto != NULL);
|
||||
aead_set_role(CRYPTO_ROLE_CLIENT);
|
||||
|
||||
int upstream = udp_socket(), first = udp_socket(), flood = udp_socket();
|
||||
struct sockaddr_in origin = { 0 }, server = { 0 };
|
||||
origin.sin_family = server.sin_family = AF_INET;
|
||||
origin.sin_addr.s_addr = server.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
server.sin_port = htons((uint16_t)port);
|
||||
assert(bind(upstream, (struct sockaddr *)&origin, sizeof(origin)) == 0);
|
||||
socklen_t length = sizeof(origin);
|
||||
assert(getsockname(upstream, (struct sockaddr *)&origin, &length) == 0);
|
||||
uint16_t upstream_port = ntohs(origin.sin_port);
|
||||
const unsigned char plaintext[] = {
|
||||
1, 127, 0, 0, 1, (unsigned char)(upstream_port >> 8),
|
||||
(unsigned char)upstream_port, 0x41
|
||||
};
|
||||
buffer_t packet = { 0 };
|
||||
balloc(&packet, 2048);
|
||||
void *first_session = NULL, *last_session = NULL;
|
||||
struct sockaddr_in first_peer = { 0 }, last_peer = { 0 };
|
||||
for (int i = 0; i < count; ++i) {
|
||||
void *session = NULL;
|
||||
memcpy(packet.data, plaintext, sizeof(plaintext));
|
||||
packet.len = sizeof(plaintext);
|
||||
assert(crypto->encrypt_udp(&packet, crypto->cipher, 2048, &session) == CRYPTO_OK);
|
||||
assert(sendto(i == 0 ? first : flood, packet.data, packet.len, 0,
|
||||
(struct sockaddr *)&server, sizeof(server)) == (ssize_t)packet.len);
|
||||
unsigned char payload[2];
|
||||
length = sizeof(last_peer);
|
||||
assert(recvfrom(upstream, payload, sizeof(payload), 0,
|
||||
(struct sockaddr *)&last_peer, &length) == 1);
|
||||
assert(payload[0] == 0x41);
|
||||
if (i == 0) {
|
||||
first_peer = last_peer;
|
||||
first_session = session;
|
||||
} else if (i == count - 1) {
|
||||
last_session = session;
|
||||
} else {
|
||||
crypto->udp_session_release(session);
|
||||
}
|
||||
}
|
||||
/* The first relay must survive eviction; the reused relay must use its
|
||||
* latest session, not a leaked or stale previous owner. */
|
||||
check_reply(upstream, first, &first_peer, crypto, &first_session, &packet);
|
||||
check_reply(upstream, flood, &last_peer, crypto, &last_session, &packet);
|
||||
crypto->udp_session_release(first_session);
|
||||
crypto->udp_session_release(last_session);
|
||||
bfree(&packet);
|
||||
close(upstream);
|
||||
close(first);
|
||||
close(flood);
|
||||
ppbloom_free();
|
||||
ss_free(crypto->cipher);
|
||||
ss_free(crypto);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user