Skip to content

Commit bd9365a

Browse files
committed
dht-f-052-fix
1 parent c0565ea commit bd9365a

4 files changed

Lines changed: 99 additions & 11 deletions

File tree

include/libwebsockets/lws-dht.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,23 @@ lws_dht_send_notify(struct lws_dht_ctx *ctx, const struct sockaddr *sa, size_t s
202202
LWS_VISIBLE LWS_EXTERN int
203203
lws_dht_msg_parse(const char *in, size_t len, struct lws_dht_msg *out);
204204

205+
/**
206+
* lws_dht_valid_domain_name() - Check a domain string is a safe DNS name
207+
*
208+
* \param domain: NUL-terminated domain string
209+
*
210+
* F-052: domain strings arriving on DHT NOTIFY datagrams are later
211+
* composed into filesystem paths (zone cache paths, lws_dir walks), so
212+
* they must be restricted to presentation-format DNS names: 1 - 253
213+
* chars of [A-Za-z0-9._-] split into non-empty labels of at most 63
214+
* chars, with an optional single trailing root dot. Anything else
215+
* (path separators, "..", empty labels, junk bytes) is refused.
216+
*
217+
* \return 1 if the name is safe, 0 otherwise
218+
*/
219+
LWS_VISIBLE LWS_EXTERN int
220+
lws_dht_valid_domain_name(const char *domain);
221+
205222
/**
206223
* lws_dht_msg_gen() - Generate a raw DHT message
207224
*

lib/misc/dht/dht.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,39 @@ lws_dht_msg_gen(char *out, size_t len, const char *verb, const char *hash, unsig
980980
return lws_snprintf(out, len, "%s %s %llu %llu ", verb, hash, offset, len_val);
981981
}
982982

983+
int
984+
lws_dht_valid_domain_name(const char *domain)
985+
{
986+
size_t i, len, label = 0;
987+
988+
if (!domain)
989+
return 0;
990+
991+
len = strlen(domain);
992+
if (!len || len > 253)
993+
return 0;
994+
995+
for (i = 0; i < len; i++) {
996+
char c = domain[i];
997+
998+
if (c == '.') {
999+
/* empty label (".." or leading dot) or overlong */
1000+
if (!label || label > 63)
1001+
return 0;
1002+
label = 0;
1003+
continue;
1004+
}
1005+
1006+
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
1007+
(c >= '0' && c <= '9') || c == '-' || c == '_'))
1008+
return 0;
1009+
label++;
1010+
}
1011+
1012+
/* final label may be empty only for a single trailing root dot */
1013+
return label || domain[len - 1] == '.';
1014+
}
1015+
9831016
int
9841017
lws_dht_msg_parse(const char *in, size_t len, struct lws_dht_msg *out)
9851018
{

minimal-examples-lowlevel/api-tests/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ api-test-gencrypto|LWS Generic Crypto apis
1010
api-test-jose|LWS JOSE apis
1111
api-test-smtp_client|SMTP client for sending emails
1212
api-test-xip|xip clipboard plugin clip chunking, parsing and reassembly
13-
api-test-dht-msg-parse|DHT RPC wire message parser, incl. hash token charset gate
13+
api-test-dht-msg-parse|DHT RPC wire message parser, incl. hash token charset and NOTIFY domain-name gates
1414

plugins/protocol_lws_dht_dnssec/protocol_lws_dht_dnssec.c

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2132,13 +2132,27 @@ cb_dht(void *closure, int event, const lws_dht_hash_t *info_hash,
21322132
((uint64_t)p[4] << 24) | ((uint64_t)p[5] << 16) |
21332133
((uint64_t)p[6] << 8) | p[7];
21342134

2135-
if (data_len > 8) {
2136-
size_t dl = data_len - 8;
2137-
if (dl > sizeof(newer_domain) - 1) dl = sizeof(newer_domain) - 1;
2138-
memcpy(newer_domain, p + 8, dl);
2139-
newer_domain[dl] = '\0';
2135+
if (data_len > 8) {
2136+
size_t dl = data_len - 8;
2137+
if (dl > sizeof(newer_domain) - 1) dl = sizeof(newer_domain) - 1;
2138+
memcpy(newer_domain, p + 8, dl);
2139+
newer_domain[dl] = '\0';
2140+
}
2141+
}
2142+
2143+
/*
2144+
* F-052: this unauthenticated datagram's domain
2145+
* string is stored in subscription state and later
2146+
* composed into filesystem paths (zone cache paths,
2147+
* lws_dir walks), so it must be a syntactically
2148+
* valid DNS name and not eg "../../x".
2149+
*/
2150+
if (newer_domain[0] &&
2151+
!lws_dht_valid_domain_name(newer_domain)) {
2152+
lwsl_notice("%s: Rejecting NOTIFY with malformed domain string\n", __func__);
2153+
break;
21402154
}
2141-
}
2155+
21422156

21432157
{
21442158
char peer_ip[64];
@@ -4444,6 +4458,18 @@ do_fetch_zone(struct lws_context *context, struct lws_dht_dnssec_fetch_zone_args
44444458

44454459
if (!strncmp(clean_domain, "dht-hash-", 9)) {
44464460
lws_strncpy(hex, clean_domain + 9, sizeof(hex));
4461+
4462+
/*
4463+
* F-052: the remainder is used verbatim for the
4464+
* lws_dir() storage walk and outgoing GET target hash,
4465+
* so it must be exactly the lowercase-hex shape the
4466+
* parser gate (F-051) would have produced.
4467+
*/
4468+
if (!dht_dnssec_hash_token_ok(hex)) {
4469+
lwsl_warn("%s: refusing fetch with malformed hash token\n", __func__);
4470+
return 1;
4471+
}
4472+
44474473
lws_hex_to_byte_array(hex, hash, (int)lws_genhash_size(LWS_DHT_STORE_GENHASH));
44484474
} else {
44494475
char domain_str[256];
@@ -4816,6 +4842,18 @@ do_subscribe_zone(struct lws_vhost *vhost, const char *domain)
48164842
for (int i = 0; i < (int)strlen(clean_domain); i++)
48174843
clean_domain[i] = (char)tolower(clean_domain[i]);
48184844

4845+
/*
4846+
* F-052 defense-in-depth: the stored domain string is later
4847+
* composed into filesystem paths (zone cache paths, lws_dir
4848+
* walks), so refuse anything that is not a syntactically valid
4849+
* DNS name. This covers ops-side callers as well as the
4850+
* cb_dht NOTIFY intake gate.
4851+
*/
4852+
if (!lws_dht_valid_domain_name(clean_domain)) {
4853+
lwsl_warn("%s: refusing subscription for malformed domain string\n", __func__);
4854+
return 1;
4855+
}
4856+
48194857
lwsl_notice("%s: Normalizing domain to %s for DHT hash calculation\n", __func__, clean_domain);
48204858
lws_snprintf(domain_str, sizeof(domain_str), "lws-dnssec-dht-%s", clean_domain);
48214859
if (lws_genhash_init(&ctx, LWS_DHT_STORE_GENHASH) ||
@@ -4828,7 +4866,7 @@ do_subscribe_zone(struct lws_vhost *vhost, const char *domain)
48284866
int exists = 0;
48294867
lws_start_foreach_dll(struct lws_dll2 *, d, lws_dll2_get_head(&vhd->subscribed_domains)) {
48304868
struct lws_dht_dnssec_subscribed_domain *sub = lws_container_of(d, struct lws_dht_dnssec_subscribed_domain, list);
4831-
if (!strcmp(sub->domain, domain)) {
4869+
if (!strcmp(sub->domain, clean_domain)) {
48324870
exists = 1;
48334871
break;
48344872
}
@@ -4838,7 +4876,7 @@ do_subscribe_zone(struct lws_vhost *vhost, const char *domain)
48384876
struct lws_dht_dnssec_subscribed_domain *nsub = malloc(sizeof(*nsub));
48394877
if (nsub) {
48404878
memset(nsub, 0, sizeof(*nsub));
4841-
lws_strncpy(nsub->domain, domain, sizeof(nsub->domain));
4879+
lws_strncpy(nsub->domain, clean_domain, sizeof(nsub->domain));
48424880
memcpy(nsub->hash, hash, (size_t)lws_genhash_size(LWS_DHT_STORE_GENHASH));
48434881

48444882
char hex[65];
@@ -4871,7 +4909,7 @@ do_subscribe_zone(struct lws_vhost *vhost, const char *domain)
48714909
struct lws_dht_dnssec_fetch_zone_args args;
48724910
memset(&args, 0, sizeof(args));
48734911
args.vhost = vhd->vhost;
4874-
args.domain = domain;
4912+
args.domain = clean_domain;
48754913
args.cache_dir = NULL;
48764914
args.cb = NULL;
48774915
args.opaque = NULL;
@@ -4881,7 +4919,7 @@ do_subscribe_zone(struct lws_vhost *vhost, const char *domain)
48814919
time_t now = time(NULL);
48824920
lws_start_foreach_dll(struct lws_dll2 *, d, lws_dll2_get_head(&vhd->subscribed_domains)) {
48834921
struct lws_dht_dnssec_subscribed_domain *sub = lws_container_of(d, struct lws_dht_dnssec_subscribed_domain, list);
4884-
if (!strcmp(sub->domain, domain)) {
4922+
if (!strcmp(sub->domain, clean_domain)) {
48854923
if (sub->needs_initial_fetch || now - sub->last_notify_fetch >= 60) {
48864924
sub->needs_initial_fetch = 0;
48874925
sub->last_notify_fetch = now;

0 commit comments

Comments
 (0)