Skip to content

Commit df82168

Browse files
committed
fix(lan): support mitigations for amplification attacks
Given a UDP packet with a spoofed address of origin, it's possible for a client to party to a minor amplification attack. Support the new `targetDeviceId` and `targetProtocolVersion` fields, to mitigate the severity of such an attempt.
1 parent 52a5d67 commit df82168

2 files changed

Lines changed: 77 additions & 4 deletions

File tree

src/plugins/lan/valent-lan-channel-service.c

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,8 @@ handshake_fiber (gpointer user_data)
257257
if (is_incoming)
258258
{
259259
g_autoptr (JsonNode) peer_identity = NULL;
260+
const char *target_device_id = NULL;
261+
int64_t target_protocol_version = 0;
260262

261263
peer_identity = dex_await_boxed (valent_packet_from_stream_future (g_io_stream_get_input_stream (data->connection),
262264
IDENTITY_BUFFER_MAX,
@@ -265,21 +267,72 @@ handshake_fiber (gpointer user_data)
265267
if (peer_identity == NULL)
266268
goto fail;
267269

270+
/* When accepting a TCP connection, the identity packet may indicate its
271+
* intended target, allowing the local device to mitigate amplification
272+
* attacks from a spoofed UDP address.
273+
*/
274+
if (valent_packet_get_string (peer_identity, "targetDeviceId", &target_device_id))
275+
{
276+
g_autofree char *local_id = valent_channel_service_dup_id (service);
277+
278+
if (g_strcmp0 (target_device_id, local_id) != 0)
279+
{
280+
g_set_error (&error,
281+
G_IO_ERROR,
282+
G_IO_ERROR_FAILED,
283+
"expected \"targetDeviceId\" field holding \"%s\"",
284+
local_id);
285+
goto fail;
286+
}
287+
}
288+
289+
if (valent_packet_get_int (peer_identity, "targetProtocolVersion", &target_protocol_version) &&
290+
target_protocol_version != VALENT_NETWORK_PROTOCOL_MAX)
291+
{
292+
g_set_error (&error,
293+
G_IO_ERROR,
294+
G_IO_ERROR_FAILED,
295+
"expected \"targetProtocolVersion\" field holding \"%u\"",
296+
VALENT_NETWORK_PROTOCOL_MAX);
297+
goto fail;
298+
}
299+
268300
data->peer_identity = g_steal_pointer (&peer_identity);
269301
}
270302
else
271303
{
304+
JsonObject *body;
305+
gboolean success;
306+
const char *target_device_id = NULL;
307+
int64_t target_protocol_version = 0;
308+
272309
data->connection = dex_await_object (_dex_socket_client_connect_to_host (data->host,
273310
data->port,
274311
cancellable),
275312
&error);
276313
if (data->connection == NULL)
277314
goto fail;
278315

279-
if (!dex_await (valent_packet_to_stream_future (g_io_stream_get_output_stream (data->connection),
280-
identity,
281-
cancellable),
282-
&error))
316+
/* When responding to a UDP broadcast, mark the identity packet for its
317+
* intended target, allowing the remote device to abort if the broadcast
318+
* address was spoofed.
319+
*/
320+
valent_packet_get_string (data->peer_identity, "deviceId", &target_device_id);
321+
valent_packet_get_int (data->peer_identity, "protocolVersion", &target_protocol_version);
322+
323+
body = valent_packet_get_body (identity);
324+
json_object_set_string_member (body, "targetDeviceId", target_device_id);
325+
json_object_set_int_member (body, "targetProtocolVersion", target_protocol_version);
326+
327+
success = dex_await (valent_packet_to_stream_future (g_io_stream_get_output_stream (data->connection),
328+
identity,
329+
cancellable),
330+
&error);
331+
// TODO: operate on a deep-copy instead?
332+
json_object_remove_member (body, "targetDeviceId");
333+
json_object_remove_member (body, "targetProtocolVersion");
334+
335+
if (!success)
283336
goto fail;
284337
}
285338

tests/plugins/lan/test-lan-plugin.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
#define TEST_OUTGOING_TLS_CERTIFICATE "/plugins/lan/outgoing-tls-certificate"
3737
#define TEST_INCOMING_TLS_IDENTITY "/plugins/lan/incoming-tls-common-name"
3838
#define TEST_OUTGOING_TLS_IDENTITY "/plugins/lan/outgoing-tls-common-name"
39+
#define TEST_UDP_SPOOFED_ADDRESS_1 "/plugins/lan/udp-spoofed-address-1"
40+
#define TEST_UDP_SPOOFED_ADDRESS_2 "/plugins/lan/udp-spoofed-address-2"
3941

4042

4143
typedef struct
@@ -706,6 +708,16 @@ static LanTestCase compliance_tests[] = {
706708
.errmsg = "*device ID does not match certificate common name*",
707709
.func = (LanFixtureFunc)test_lan_service_outgoing_broadcast,
708710
},
711+
{
712+
.name = TEST_UDP_SPOOFED_ADDRESS_1,
713+
.errmsg = "*expected \"targetDeviceId\" field holding*",
714+
.func = (LanFixtureFunc)test_lan_service_outgoing_broadcast,
715+
},
716+
{
717+
.name = TEST_UDP_SPOOFED_ADDRESS_2,
718+
.errmsg = "*expected \"targetProtocolVersion\" field holding*",
719+
.func = (LanFixtureFunc)test_lan_service_outgoing_broadcast,
720+
},
709721
};
710722

711723
static void
@@ -775,6 +787,14 @@ test_lan_service_compliance_test (gconstpointer user_data)
775787
g_clear_object (&fixture->peer_certificate);
776788
fixture->peer_certificate = valent_certificate_new_sync (NULL, NULL);
777789
}
790+
else if (g_strcmp0 (test_name, TEST_UDP_SPOOFED_ADDRESS_1) == 0)
791+
{
792+
json_object_set_string_member (body, "targetDeviceId", "27456e3cfe5c420896a7c0caeec5e5a0");
793+
}
794+
else if (g_strcmp0 (test_name, TEST_UDP_SPOOFED_ADDRESS_2) == 0)
795+
{
796+
json_object_set_int_member (body, "targetProtocolVersion", 7);
797+
}
778798

779799
test_case->func (fixture, test_case);
780800
lan_service_fixture_tear_down (fixture, test_case);

0 commit comments

Comments
 (0)