mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-22 14:11:18 +00:00
Terminate DNS diagnostics after partial address conversion
This commit is contained in:
1 parent
3ede9b2155
commit
be2ab346b0
3 files changed
+68
-3
No files matched your search
@@ -55,7 +55,11 @@ static void formatDnsList(char* out, size_t out_size, const ip_addr_t* servers,
|
||||
if (ip_addr_isany_val(servers[i])) {
|
||||
snprintf(out + len, out_size - len, "-");
|
||||
} else {
|
||||
ipaddr_ntoa_r(&servers[i], out + len, (int)(out_size - len));
|
||||
if (ipaddr_ntoa_r(&servers[i], out + len, (int)(out_size - len)) == nullptr) {
|
||||
// lwIP can leave a partial address without NUL when the buffer fills.
|
||||
out[out_size - 1] = '\0';
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+47
-2
@@ -36,8 +36,13 @@ static const ip_addr_t empty_address;
|
||||
#define IP_ADDR_ANY (&empty_address)
|
||||
static bool ip_addr_isany_val(const ip_addr_t& value) { return !value.text[0]; }
|
||||
static char* ipaddr_ntoa_r(const ip_addr_t* value, char* out, int size) {
|
||||
if (size <= 0 || strlen(value->text.data()) >= size_t(size)) return nullptr;
|
||||
strcpy(out, value->text.data());
|
||||
if (size <= 0) return nullptr;
|
||||
const size_t length = strlen(value->text.data());
|
||||
// Match lwIP's partial-write behavior: a short buffer can receive address
|
||||
// bytes without a terminating NUL before conversion reports failure.
|
||||
const size_t written = length < size_t(size) ? length + 1 : size_t(size);
|
||||
memcpy(out, value->text.data(), written);
|
||||
if (length >= size_t(size)) return nullptr;
|
||||
return out;
|
||||
}
|
||||
using esp_err_t = int;
|
||||
@@ -241,6 +246,44 @@ static void format_scenario() {
|
||||
require(dns_writes.size() == writes_before, "read-only DNS diagnostics changed resolver state");
|
||||
}
|
||||
|
||||
static void ipv6_scenario() {
|
||||
const char* longest = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff";
|
||||
global_dns = {{address(longest, 6), address(longest, 6), address(longest, 6)}};
|
||||
const std::string full = std::string(longest) + "," + longest + "," + longest;
|
||||
for (size_t size = 1; size <= 128; ++size) {
|
||||
std::array<unsigned char, 160> bounded;
|
||||
bounded.fill(0xA5);
|
||||
char* out = reinterpret_cast<char*>(bounded.data() + 8);
|
||||
formatDnsList(out, size, global_dns.data(), 3);
|
||||
for (size_t i = 0; i < 8; ++i) require(bounded[i] == 0xA5, "IPv6 DNS list wrote before destination");
|
||||
for (size_t i = 8 + size; i < bounded.size(); ++i) require(bounded[i] == 0xA5, "IPv6 DNS list exceeded capacity");
|
||||
require(memchr(out, '\0', size) != nullptr, "IPv6 DNS list lost terminator");
|
||||
require(std::string(out) == full.substr(0, size - 1), "IPv6 DNS list changed truncated prefix");
|
||||
}
|
||||
automatic._wifi.snapshotDns();
|
||||
automatic._ethernet.snapshotDns();
|
||||
automatic._selected = NetworkMedium::Ethernet;
|
||||
for (const NetworkLink* link : {static_cast<NetworkLink*>(&automatic._wifi),
|
||||
static_cast<NetworkLink*>(&automatic._ethernet),
|
||||
static_cast<NetworkLink*>(&automatic)}) {
|
||||
struct Reply { unsigned char before[8]; char text[160]; unsigned char after[8]; } reply;
|
||||
memset(&reply, 0xA5, sizeof(reply));
|
||||
link->formatDns(reply.text, sizeof(reply.text));
|
||||
require(memchr(reply.text, '\0', sizeof(reply.text)) != nullptr, "IPv6 DNS diagnostic lost terminator");
|
||||
require(strncmp(reply.text, "> dns:", 6) == 0, "IPv6 DNS diagnostic lost live resolver prefix");
|
||||
for (unsigned char c : reply.before) require(c == 0xA5, "IPv6 DNS diagnostic underflowed reply");
|
||||
for (unsigned char c : reply.after) require(c == 0xA5, "IPv6 DNS diagnostic overflowed reply");
|
||||
}
|
||||
}
|
||||
|
||||
static void ipv6_termination_scenario() {
|
||||
const ip_addr_t server = address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 6);
|
||||
char out[8];
|
||||
memset(out, 0xA5, sizeof(out));
|
||||
formatDnsList(out, sizeof(out), &server, 1);
|
||||
require(memchr(out, '\0', sizeof(out)) != nullptr, "IPv6 DNS list lost terminator");
|
||||
}
|
||||
|
||||
static void zero_scenario() {
|
||||
char canary = 'Z';
|
||||
formatDnsList(&canary, 0, global_dns.data(), 3);
|
||||
@@ -272,6 +315,8 @@ int main(int argc, char** argv) {
|
||||
else if (scenario == "never-leased") never_leased_scenario();
|
||||
else if (scenario == "renewal") renewal_scenario();
|
||||
else if (scenario == "format") format_scenario();
|
||||
else if (scenario == "ipv6") ipv6_scenario();
|
||||
else if (scenario == "ipv6-termination") ipv6_termination_scenario();
|
||||
else if (scenario == "zero") zero_scenario();
|
||||
else if (scenario == "getter") getter_scenario();
|
||||
else throw std::runtime_error("unknown DNS scenario");
|
||||
|
||||
@@ -98,6 +98,22 @@ class NetworkDnsTest(unittest.TestCase):
|
||||
def test_zero_capacity_and_null_destination_do_not_write(self):
|
||||
self.run_scenario("zero")
|
||||
|
||||
def test_ipv6_partial_conversion_keeps_lists_and_diagnostics_terminated(self):
|
||||
self.run_scenario("ipv6")
|
||||
self.run_scenario("ipv6-termination")
|
||||
|
||||
def test_unchecked_ipv6_conversion_loses_terminator(self):
|
||||
source = (ROOT / "src/helpers/NetworkLink.cpp").read_text(encoding="utf-8")
|
||||
guarded = extract_braced(source, "if (ipaddr_ntoa_r(")
|
||||
broken = self.source.replace(guarded,
|
||||
"ipaddr_ntoa_r(&servers[i], out + len, (int)(out_size - len));")
|
||||
self.assertNotEqual(broken, self.source)
|
||||
binary = self.compile(broken, "unchecked-ipv6")
|
||||
result = subprocess.run([str(binary), "ipv6-termination"], capture_output=True,
|
||||
text=True, timeout=10)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("IPv6 DNS list lost terminator", result.stderr)
|
||||
|
||||
def test_observer_getter_uses_real_interface_and_exact_command(self):
|
||||
self.run_scenario("getter")
|
||||
cli = (ROOT / "src/helpers/CommonCLI_Observer.cpp").read_text(encoding="utf-8")
|
||||
|
||||
Reference in new issue
Block a user