Skip to content

Commit ff5445a

Browse files
no9248cf
andcommitted
options/posix: add bound checks for dns response parsing
Co-authored-by: 48cf <32851089+48cf@users.noreply.github.com>
1 parent b475232 commit ff5445a

File tree

3 files changed

+220
-83
lines changed

3 files changed

+220
-83
lines changed

options/posix/generic/lookup.cpp

Lines changed: 149 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -12,40 +12,82 @@
1212
#include <stdio.h>
1313
#include <ctype.h>
1414

15-
namespace mlibc {
16-
1715
namespace {
18-
constexpr unsigned int RECORD_A = 1;
19-
constexpr unsigned int RECORD_CNAME = 5;
20-
constexpr unsigned int RECORD_PTR = 12;
21-
}
2216

23-
static frg::string<MemoryAllocator> read_dns_name(char *buf, char *&it) {
17+
enum class DnsClass : uint16_t {
18+
IN = 1,
19+
};
20+
21+
enum class ResourceRecord : uint16_t {
22+
A = 1,
23+
NS = 2,
24+
CNAME = 5,
25+
PTR = 12,
26+
TXT = 16,
27+
AAAA = 28,
28+
OPT = 41,
29+
};
30+
31+
frg::optional<frg::string<MemoryAllocator>> read_dns_name(frg::span<uint8_t> buf, size_t &offset) {
2432
frg::string<MemoryAllocator> res{getAllocator()};
25-
while (true) {
26-
char code = *it++;
27-
if ((code & 0xC0) == 0xC0) {
33+
while(offset < buf.size()) {
34+
uint8_t code = buf[offset++];
35+
36+
if((code & 0xC0) == 0xC0) {
2837
// pointer
29-
uint8_t offset = ((code & 0x3F) << 8) | *it++;
30-
auto offset_it = buf + offset;
31-
return res + read_dns_name(buf, offset_it);
32-
} else if (!(code & 0xC0)) {
33-
if (!code)
34-
break;
38+
if(offset + 1 > buf.size()) {
39+
return frg::null_opt;
40+
}
41+
42+
uint8_t pointer_offset = ((code & 0x3F) << 8) | buf[offset++];
43+
if(pointer_offset >= buf.size()) {
44+
return frg::null_opt;
45+
}
46+
47+
size_t sub_offset = pointer_offset;
48+
auto sub_name = read_dns_name(buf, sub_offset);
49+
if(!sub_name) {
50+
return frg::null_opt;
51+
}
52+
53+
return res + *sub_name;
54+
} else if(!(code & 0xC0)) {
55+
if (!code) {
56+
return res;
57+
} else if(offset + code > buf.size()) {
58+
return frg::null_opt;
59+
}
3560

36-
for (int i = 0; i < code; i++)
37-
res += (*it++);
61+
for (int i = 0; i < code; i++) {
62+
res += buf[offset++];
63+
}
3864

39-
if (*it)
65+
if (offset < buf.size() && buf[offset]) {
4066
res += '.';
67+
}
4168
} else {
42-
break;
69+
return frg::null_opt;
4370
}
4471
}
4572

46-
return res;
73+
return frg::null_opt;
74+
}
75+
76+
void writeUint16(frg::string<MemoryAllocator> &request, uint16_t val) {
77+
request += static_cast<char>(val >> 8);
78+
request += static_cast<char>(val & 0xFF);
79+
};
80+
81+
uint16_t readUint16(frg::span<uint8_t> buf, size_t offset) {
82+
uint16_t val = 0;
83+
memcpy(&val, buf.data() + offset, sizeof(val));
84+
return ntohs(val);
4785
}
4886

87+
} // namespace
88+
89+
namespace mlibc {
90+
4991
int lookup_name_dns(struct lookup_result &buf, const char *name,
5092
frg::string<MemoryAllocator> &canon_name) {
5193
frg::string<MemoryAllocator> request{getAllocator()};
@@ -73,12 +115,8 @@ int lookup_name_dns(struct lookup_result &buf, const char *name,
73115
}
74116

75117
request += char(0);
76-
// set question type to fetch A records
77-
request += 0;
78-
request += 1;
79-
// set CLASS to IN
80-
request += 0;
81-
request += 1;
118+
writeUint16(request, static_cast<uint16_t>(ResourceRecord::A));
119+
writeUint16(request, static_cast<uint16_t>(DnsClass::IN));
82120

83121
struct sockaddr_in sin = {};
84122
sin.sin_family = AF_INET;
@@ -105,48 +143,69 @@ int lookup_name_dns(struct lookup_result &buf, const char *name,
105143
return -EAI_SYSTEM;
106144
}
107145

108-
char response[256];
146+
uint8_t response[512];
109147
ssize_t rlen;
110148
int num_ans = 0;
111-
while ((rlen = recvfrom(fd, response, 256, 0, NULL, NULL)) >= 0) {
149+
while ((rlen = recvfrom(fd, response, sizeof(response), 0, NULL, NULL)) >= 0) {
112150
if ((size_t)rlen < sizeof(struct dns_header))
113151
continue;
114-
auto response_header = reinterpret_cast<struct dns_header*>(response);
152+
auto response_header = reinterpret_cast<struct dns_header *>(response);
115153
if (response_header->identification != header.identification)
116154
return -EAI_FAIL;
117155

118-
auto it = response + sizeof(struct dns_header);
156+
auto view = frg::span<uint8_t>{response, static_cast<size_t>(rlen)};
157+
size_t offset = sizeof(struct dns_header);
158+
119159
for (int i = 0; i < ntohs(response_header->no_q); i++) {
120-
auto dns_name = read_dns_name(response, it);
121-
(void) dns_name;
122-
it += 4;
160+
auto dns_name = read_dns_name(view, offset);
161+
if(!dns_name)
162+
return -EAI_FAIL;
163+
164+
// skip type and class
165+
offset += 4;
123166
}
124167

125168
for (int i = 0; i < ntohs(response_header->no_ans); i++) {
169+
auto dns_name = read_dns_name(view, offset);
170+
if(!dns_name) {
171+
return -EAI_FAIL;
172+
} else if(offset + 10 > view.size()) {
173+
return -EAI_FAIL;
174+
}
175+
176+
ResourceRecord rrType{readUint16(view, offset + 0)};
177+
DnsClass rrClass{readUint16(view, offset + 2)};
178+
// ignore TTL
179+
uint16_t rrLength = readUint16(view, offset + 8);
180+
offset += 10;
181+
182+
if(rrClass != DnsClass::IN || offset + rrLength > view.size()) {
183+
return -EAI_FAIL;
184+
}
185+
126186
struct dns_addr_buf buffer;
127-
auto dns_name = read_dns_name(response, it);
128-
129-
uint16_t rr_type = (it[0] << 8) | it[1];
130-
uint16_t rr_class = (it[2] << 8) | it[3];
131-
uint16_t rr_length = (it[8] << 8) | it[9];
132-
it += 10;
133-
(void)rr_class;
134-
135-
switch (rr_type) {
136-
case RECORD_A:
137-
memcpy(buffer.addr, it, rr_length);
138-
it += rr_length;
187+
switch (rrType) {
188+
case ResourceRecord::A:
189+
memcpy(buffer.addr, &view[offset], rrLength);
190+
offset += rrLength;
139191
buffer.family = AF_INET;
140-
buffer.name = std::move(dns_name);
192+
buffer.name = std::move(*dns_name);
141193
buf.buf.push(std::move(buffer));
142194
break;
143-
case RECORD_CNAME:
144-
canon_name = read_dns_name(response, it);
145-
buf.aliases.push(std::move(dns_name));
195+
case ResourceRecord::CNAME: {
196+
size_t suboffset = offset;
197+
auto cname = read_dns_name(view, suboffset);
198+
if(!cname) {
199+
return -EAI_FAIL;
200+
}
201+
canon_name = std::move(*cname);
202+
buf.aliases.push(std::move(*dns_name));
203+
offset = suboffset;
146204
break;
205+
}
147206
default:
148207
mlibc::infoLogger() << "lookup_name_dns: unknown rr type "
149-
<< rr_type << frg::endlog;
208+
<< static_cast<uint16_t>(rrType) << frg::endlog;
150209
break;
151210
}
152211
}
@@ -175,7 +234,7 @@ int lookup_addr_dns(frg::span<char> name, frg::array<uint8_t, 16> &addr, int fam
175234
request.resize(sizeof(header));
176235
memcpy(request.data(), &header, sizeof(header));
177236

178-
char addr_str[64];
237+
char addr_str[INET6_ADDRSTRLEN];
179238
if(!inet_ntop(family, addr.data(), addr_str, sizeof(addr_str))) {
180239
switch(errno) {
181240
case EAFNOSUPPORT:
@@ -201,13 +260,8 @@ int lookup_addr_dns(frg::span<char> name, frg::array<uint8_t, 16> &addr, int fam
201260
} while(ptr != 0);
202261

203262
request += char(0);
204-
// set question type to fetch PTR records
205-
request += 0;
206-
request += 12;
207-
// set CLASS to IN
208-
request += 0;
209-
request += 1;
210-
263+
writeUint16(request, static_cast<uint16_t>(ResourceRecord::PTR));
264+
writeUint16(request, static_cast<uint16_t>(DnsClass::IN));
211265

212266
struct sockaddr_in sin = {};
213267
sin.sin_family = AF_INET;
@@ -234,48 +288,62 @@ int lookup_addr_dns(frg::span<char> name, frg::array<uint8_t, 16> &addr, int fam
234288
return -EAI_SYSTEM;
235289
}
236290

237-
char response[256];
291+
uint8_t response[512];
238292
ssize_t rlen;
239293
int num_ans = 0;
240-
while ((rlen = recvfrom(fd, response, 256, 0, NULL, NULL)) >= 0) {
294+
while ((rlen = recvfrom(fd, response, sizeof(response), 0, NULL, NULL)) >= 0) {
241295
if ((size_t)rlen < sizeof(struct dns_header))
242296
continue;
243297
auto response_header = reinterpret_cast<struct dns_header*>(response);
244298
if (response_header->identification != header.identification)
245299
return -EAI_FAIL;
246300

247-
auto it = response + sizeof(struct dns_header);
301+
frg::span<uint8_t> view{response, static_cast<size_t>(rlen)};
302+
size_t offset = sizeof(struct dns_header);
303+
248304
for (int i = 0; i < ntohs(response_header->no_q); i++) {
249-
auto dns_name = read_dns_name(response, it);
250-
(void) dns_name;
251-
it += 4;
305+
auto dns_name = read_dns_name(view, offset);
306+
if(!dns_name)
307+
return -EAI_FAIL;
308+
309+
offset += 4;
252310
}
253311

254312
for (int i = 0; i < ntohs(response_header->no_ans); i++) {
255-
struct dns_addr_buf buffer;
256-
auto dns_name = read_dns_name(response, it);
313+
auto dns_name = read_dns_name(view, offset);
314+
if(!dns_name) {
315+
return -EAI_FAIL;
316+
} else if(offset + 10 > view.size()) {
317+
return -EAI_FAIL;
318+
}
257319

258-
uint16_t rr_type = (it[0] << 8) | it[1];
259-
uint16_t rr_class = (it[2] << 8) | it[3];
260-
uint16_t rr_length = (it[8] << 8) | it[9];
261-
it += 10;
262-
(void)rr_class;
263-
(void)rr_length;
320+
ResourceRecord rrType{readUint16(view, offset + 0)};
321+
DnsClass rrClass{readUint16(view, offset + 2)};
322+
// ignore TTL
323+
uint16_t rrLength = readUint16(view, offset + 8);
264324

265-
(void)dns_name;
325+
offset += 10;
266326

267-
switch (rr_type) {
268-
case RECORD_PTR: {
269-
auto ptr_name = read_dns_name(response, it);
270-
if (ptr_name.size() >= name.size())
327+
if(rrClass != DnsClass::IN || offset + rrLength > view.size()) {
328+
return -EAI_FAIL;
329+
}
330+
331+
struct dns_addr_buf buffer;
332+
switch (rrType) {
333+
case ResourceRecord::PTR: {
334+
auto ptr_name = read_dns_name(view, offset);
335+
if(!ptr_name) {
336+
return -EAI_FAIL;
337+
} else if(ptr_name->size() >= name.size()) {
271338
return -EAI_OVERFLOW;
272-
std::copy(ptr_name.begin(), ptr_name.end(), name.data());
273-
name.data()[ptr_name.size()] = '\0';
339+
}
340+
std::copy(ptr_name->begin(), ptr_name->end(), name.data());
341+
name.data()[ptr_name->size()] = '\0';
274342
return 1;
275343
}
276344
default:
277345
mlibc::infoLogger() << "lookup_addr_dns: unknown rr type "
278-
<< rr_type << frg::endlog;
346+
<< static_cast<uint16_t>(rrType) << frg::endlog;
279347
break;
280348
}
281349
num_ans += ntohs(response_header->no_ans);

0 commit comments

Comments
 (0)