diff --git a/options/posix/generic/lookup.cpp b/options/posix/generic/lookup.cpp index cc98c353de..62de32759b 100644 --- a/options/posix/generic/lookup.cpp +++ b/options/posix/generic/lookup.cpp @@ -12,40 +12,82 @@ #include #include -namespace mlibc { - namespace { - constexpr unsigned int RECORD_A = 1; - constexpr unsigned int RECORD_CNAME = 5; - constexpr unsigned int RECORD_PTR = 12; -} -static frg::string read_dns_name(char *buf, char *&it) { +enum class DnsClass : uint16_t { + IN = 1, +}; + +enum class ResourceRecord : uint16_t { + A = 1, + NS = 2, + CNAME = 5, + PTR = 12, + TXT = 16, + AAAA = 28, + OPT = 41, +}; + +frg::optional> read_dns_name(frg::span buf, size_t &offset) { frg::string res{getAllocator()}; - while (true) { - char code = *it++; - if ((code & 0xC0) == 0xC0) { + while(offset < buf.size()) { + uint8_t code = buf[offset++]; + + if((code & 0xC0) == 0xC0) { // pointer - uint8_t offset = ((code & 0x3F) << 8) | *it++; - auto offset_it = buf + offset; - return res + read_dns_name(buf, offset_it); - } else if (!(code & 0xC0)) { - if (!code) - break; + if(offset + 1 > buf.size()) { + return frg::null_opt; + } + + uint8_t pointer_offset = ((code & 0x3F) << 8) | buf[offset++]; + if(pointer_offset >= buf.size()) { + return frg::null_opt; + } + + size_t sub_offset = pointer_offset; + auto sub_name = read_dns_name(buf, sub_offset); + if(!sub_name) { + return frg::null_opt; + } + + return res + *sub_name; + } else if(!(code & 0xC0)) { + if (!code) { + return res; + } else if(offset + code > buf.size()) { + return frg::null_opt; + } - for (int i = 0; i < code; i++) - res += (*it++); + for (int i = 0; i < code; i++) { + res += buf[offset++]; + } - if (*it) + if (offset < buf.size() && buf[offset]) { res += '.'; + } } else { - break; + return frg::null_opt; } } - return res; + return frg::null_opt; +} + +void writeUint16(frg::string &request, uint16_t val) { + request += static_cast(val >> 8); + request += static_cast(val & 0xFF); +}; + +uint16_t readUint16(frg::span buf, size_t offset) { + uint16_t val = 0; + memcpy(&val, buf.data() + offset, sizeof(val)); + return ntohs(val); } +} // namespace + +namespace mlibc { + int lookup_name_dns(struct lookup_result &buf, const char *name, frg::string &canon_name) { frg::string request{getAllocator()}; @@ -73,12 +115,8 @@ int lookup_name_dns(struct lookup_result &buf, const char *name, } request += char(0); - // set question type to fetch A records - request += 0; - request += 1; - // set CLASS to IN - request += 0; - request += 1; + writeUint16(request, static_cast(ResourceRecord::A)); + writeUint16(request, static_cast(DnsClass::IN)); struct sockaddr_in sin = {}; sin.sin_family = AF_INET; @@ -105,48 +143,69 @@ int lookup_name_dns(struct lookup_result &buf, const char *name, return -EAI_SYSTEM; } - char response[256]; + uint8_t response[512]; ssize_t rlen; int num_ans = 0; - while ((rlen = recvfrom(fd, response, 256, 0, NULL, NULL)) >= 0) { + while ((rlen = recvfrom(fd, response, sizeof(response), 0, NULL, NULL)) >= 0) { if ((size_t)rlen < sizeof(struct dns_header)) continue; - auto response_header = reinterpret_cast(response); + auto response_header = reinterpret_cast(response); if (response_header->identification != header.identification) return -EAI_FAIL; - auto it = response + sizeof(struct dns_header); + auto view = frg::span{response, static_cast(rlen)}; + size_t offset = sizeof(struct dns_header); + for (int i = 0; i < ntohs(response_header->no_q); i++) { - auto dns_name = read_dns_name(response, it); - (void) dns_name; - it += 4; + auto dns_name = read_dns_name(view, offset); + if(!dns_name) + return -EAI_FAIL; + + // skip type and class + offset += 4; } for (int i = 0; i < ntohs(response_header->no_ans); i++) { + auto dns_name = read_dns_name(view, offset); + if(!dns_name) { + return -EAI_FAIL; + } else if(offset + 10 > view.size()) { + return -EAI_FAIL; + } + + ResourceRecord rrType{readUint16(view, offset + 0)}; + DnsClass rrClass{readUint16(view, offset + 2)}; + // ignore TTL + uint16_t rrLength = readUint16(view, offset + 8); + offset += 10; + + if(rrClass != DnsClass::IN || offset + rrLength > view.size()) { + return -EAI_FAIL; + } + struct dns_addr_buf buffer; - auto dns_name = read_dns_name(response, it); - - uint16_t rr_type = (it[0] << 8) | it[1]; - uint16_t rr_class = (it[2] << 8) | it[3]; - uint16_t rr_length = (it[8] << 8) | it[9]; - it += 10; - (void)rr_class; - - switch (rr_type) { - case RECORD_A: - memcpy(buffer.addr, it, rr_length); - it += rr_length; + switch (rrType) { + case ResourceRecord::A: + memcpy(buffer.addr, &view[offset], rrLength); + offset += rrLength; buffer.family = AF_INET; - buffer.name = std::move(dns_name); + buffer.name = std::move(*dns_name); buf.buf.push(std::move(buffer)); break; - case RECORD_CNAME: - canon_name = read_dns_name(response, it); - buf.aliases.push(std::move(dns_name)); + case ResourceRecord::CNAME: { + size_t suboffset = offset; + auto cname = read_dns_name(view, suboffset); + if(!cname) { + return -EAI_FAIL; + } + canon_name = std::move(*cname); + buf.aliases.push(std::move(*dns_name)); + offset = suboffset; break; + } default: mlibc::infoLogger() << "lookup_name_dns: unknown rr type " - << rr_type << frg::endlog; + << static_cast(rrType) << frg::endlog; break; } } @@ -175,7 +234,7 @@ int lookup_addr_dns(frg::span name, frg::array &addr, int fam request.resize(sizeof(header)); memcpy(request.data(), &header, sizeof(header)); - char addr_str[64]; + char addr_str[INET6_ADDRSTRLEN]; if(!inet_ntop(family, addr.data(), addr_str, sizeof(addr_str))) { switch(errno) { case EAFNOSUPPORT: @@ -201,13 +260,8 @@ int lookup_addr_dns(frg::span name, frg::array &addr, int fam } while(ptr != 0); request += char(0); - // set question type to fetch PTR records - request += 0; - request += 12; - // set CLASS to IN - request += 0; - request += 1; - + writeUint16(request, static_cast(ResourceRecord::PTR)); + writeUint16(request, static_cast(DnsClass::IN)); struct sockaddr_in sin = {}; sin.sin_family = AF_INET; @@ -234,48 +288,62 @@ int lookup_addr_dns(frg::span name, frg::array &addr, int fam return -EAI_SYSTEM; } - char response[256]; + uint8_t response[512]; ssize_t rlen; int num_ans = 0; - while ((rlen = recvfrom(fd, response, 256, 0, NULL, NULL)) >= 0) { + while ((rlen = recvfrom(fd, response, sizeof(response), 0, NULL, NULL)) >= 0) { if ((size_t)rlen < sizeof(struct dns_header)) continue; auto response_header = reinterpret_cast(response); if (response_header->identification != header.identification) return -EAI_FAIL; - auto it = response + sizeof(struct dns_header); + frg::span view{response, static_cast(rlen)}; + size_t offset = sizeof(struct dns_header); + for (int i = 0; i < ntohs(response_header->no_q); i++) { - auto dns_name = read_dns_name(response, it); - (void) dns_name; - it += 4; + auto dns_name = read_dns_name(view, offset); + if(!dns_name) + return -EAI_FAIL; + + offset += 4; } for (int i = 0; i < ntohs(response_header->no_ans); i++) { - struct dns_addr_buf buffer; - auto dns_name = read_dns_name(response, it); + auto dns_name = read_dns_name(view, offset); + if(!dns_name) { + return -EAI_FAIL; + } else if(offset + 10 > view.size()) { + return -EAI_FAIL; + } - uint16_t rr_type = (it[0] << 8) | it[1]; - uint16_t rr_class = (it[2] << 8) | it[3]; - uint16_t rr_length = (it[8] << 8) | it[9]; - it += 10; - (void)rr_class; - (void)rr_length; + ResourceRecord rrType{readUint16(view, offset + 0)}; + DnsClass rrClass{readUint16(view, offset + 2)}; + // ignore TTL + uint16_t rrLength = readUint16(view, offset + 8); - (void)dns_name; + offset += 10; - switch (rr_type) { - case RECORD_PTR: { - auto ptr_name = read_dns_name(response, it); - if (ptr_name.size() >= name.size()) + if(rrClass != DnsClass::IN || offset + rrLength > view.size()) { + return -EAI_FAIL; + } + + struct dns_addr_buf buffer; + switch (rrType) { + case ResourceRecord::PTR: { + auto ptr_name = read_dns_name(view, offset); + if(!ptr_name) { + return -EAI_FAIL; + } else if(ptr_name->size() >= name.size()) { return -EAI_OVERFLOW; - std::copy(ptr_name.begin(), ptr_name.end(), name.data()); - name.data()[ptr_name.size()] = '\0'; + } + std::copy(ptr_name->begin(), ptr_name->end(), name.data()); + name.data()[ptr_name->size()] = '\0'; return 1; } default: mlibc::infoLogger() << "lookup_addr_dns: unknown rr type " - << rr_type << frg::endlog; + << static_cast(rrType) << frg::endlog; break; } num_ans += ntohs(response_header->no_ans); diff --git a/tests/meson.build b/tests/meson.build index 9f4b3943d9..903ef77619 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -117,6 +117,10 @@ all_test_cases = [ 'linux/getifaddrs', ] +all_cpp_test_cases = [ + 'posix/lookup', +] + if host_machine.system() == 'linux' all_test_cases += 'glibc/linux-syscall' endif @@ -150,7 +154,9 @@ extra_cflags_test_cases = { test_sources = [] test_objects = [] test_link_args = [] +test_cpp_link_args = ['-static'] test_c_args = ['-D_GNU_SOURCE', '-fno-builtin'] +test_cpp_args = ['-D_GNU_SOURCE', '-fno-builtin'] use_pie = false host_test_c_args = ['-fno-builtin'] @@ -162,6 +168,7 @@ if c_compiler.get_id() == 'gcc' host_test_c_args += [] elif c_compiler.get_id() == 'clang' test_c_args += ['-Wno-unknown-warning-option'] + test_cpp_args += ['-Wno-unknown-warning-option'] host_test_c_args += ['-Wno-unknown-warning-option'] endif @@ -169,7 +176,7 @@ endif # since it is internal to libc.so and ld.so. test_override_options = ['b_sanitize=none'] -if library_type == 'static' +if library_type in ['both', 'static'] c_compiler = meson.get_compiler('c') searchdirs = run_command(c_compiler.cmd_array(), '-print-search-dirs', check: true).stdout() @@ -201,7 +208,18 @@ if library_type == 'static' if crtpath == '' error('could not find crtbegin.o/crtend.o') endif - test_objects += [crtpath / 'crtbegin.o', crtpath / 'crtend.o'] + + test_static_objects = [crtpath / 'crtbegin.o', crtpath / 'crtend.o'] + + libc_static_dep = declare_dependency( + include_directories: libc_include_dirs, + link_with: libc_static, + dependencies: [libc_deps, rtlib_deps] + ) +endif + +if library_type == 'static' + test_objects += test_static_objects libc_dep = declare_dependency( include_directories: libc_include_dirs, @@ -211,6 +229,7 @@ if library_type == 'static' use_pie = false test_c_args += '-no-pie' test_link_args += ['-no-pie', '-static'] + test_cpp_link_args += ['-no-pie', '-static'] test_sources += crt else libc_dep = declare_dependency( @@ -304,3 +323,34 @@ foreach test_name : all_test_cases test(test_short_name, exec, suite: ['host-libc', test_subdir], should_fail: should_fail, timeout: timeout_sec) endif endforeach + +if library_type in ['both', 'static'] and c_compiler.get_id() == 'gcc' + foreach test_name : all_cpp_test_cases + test_subdir = test_name.split('/')[0] + test_short_name = test_name.split('/')[1] + test_exec_name = test_name.replace('/', '-') + + if test_subdir == 'bsd' and not bsd_option + continue + elif test_subdir == 'glibc' and not glibc_option + continue + elif test_subdir == 'posix' and not posix_option + continue + elif test_subdir == 'linux' and not linux_option + continue + endif + + should_fail = fail_test_cases.contains(test_name) + exec = executable(test_exec_name, [test_name + '.cpp'] + crt, + dependencies: libc_static_dep, + objects: test_static_objects, + build_rpath: meson.build_root(), + override_options: test_override_options, + cpp_args: test_cpp_args + static_cpp_args, + link_args: test_cpp_link_args, + pie: false, + ) + + test(test_short_name, exec, suite: test_subdir, should_fail: should_fail, timeout: timeout_sec) + endforeach +endif diff --git a/tests/posix/lookup.cpp b/tests/posix/lookup.cpp new file mode 100644 index 0000000000..6b8d7f7da6 --- /dev/null +++ b/tests/posix/lookup.cpp @@ -0,0 +1,19 @@ +#include +#include +#include + +int main() { + struct mlibc::lookup_result res; + frg::string canon_name{getAllocator(), "google.com"}; + + auto ret = mlibc::lookup_name_dns(res, "google.com", canon_name); + if(ret > 0) { + char buf[INET6_ADDRSTRLEN]; + inet_ntop(res.buf.front().family, res.buf.front().addr, buf, sizeof(buf)); + fprintf(stderr, "Lookup of google.com returns: '%s'\n", buf); + } else { + return 1; + } + + return 0; +}