Skip to main content

Command Palette

Search for a command to run...

Triaging CVE-2017-14492: dnsmasq Heap Overflow with Our Platform

Analyzing Heap buffer overflow in dnsmasq via IPv6 RA packets

Updated
5 min read
Triaging CVE-2017-14492: dnsmasq Heap Overflow with Our Platform
A

At Arkcore, we are a team of cybersecurity experts, engineers, and vulnerability researchers driven by a single mission — to make firmware and embedded systems secure by design. Born from a deep curiosity about low-level systems and how they fail, Arkcore helps enterprises proactively discover, assess, and remediate vulnerabilities that traditional tools miss. With deep expertise in firmware security, embedded system security, and real-world vulnerability research, we deliver actionable insights, threat intelligence, and compliance solutions that protect connected devices across industries.

Our Value Proposition • Reduce Noise: Prioritised, actionable results with minimal false positives. • Triage Firmware Risks with Precision: Identify, assess, and resolve real threats. • Accelerate Compliance & Delivery: Stay audit-ready and meet regulations like ISO 21434, R155, and WP.29.

Firmware Visibility + Threat Intelligence = Proactive Protection

Our platform, Panopticon, empowers organisations with: • Real-time threat visibility across firmware • Automated firmware analysis • SaaS or on-premise deployment options • Seamless compliance with Cyber Resilience Act, AIS 189, and other global standards • Future-ready asset and vulnerability management across all hardware

Secure at Core: Comprehensive Firmware Security at Every Layer Panopticon provides an end-to-end solution for analyzing, monitoring, and securing firmware throughout the entire product lifecycle. Key Differentiators • Complete Component Visibility: Identify vulnerabilities (known and unknown) in third-party libraries, open-source components, and proprietary code. • Continuous Monitoring: Monitor firmware post-deployment for emerging threats. • Build Trust & Compliance: Become a secure, compliant brand with full regulatory alignment.

Reach out to us at hello@arkcore.io for Securing Future of Your Products !

void icmp6_packet(time_t now)
{
  char interface[IF_NAMESIZE+1];
  ssize_t sz; 
  int if_index = 0;
  struct cmsghdr *cmptr;
  struct msghdr msg;
  union {
    struct cmsghdr align; /* this ensures alignment */
    char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
  } control_u;
  struct sockaddr_in6 from;
  unsigned char *packet;
  struct iname *tmp;

  /* Note: use outpacket for input buffer */
  msg.msg_control = control_u.control6;
  msg.msg_controllen = sizeof(control_u);
  msg.msg_flags = 0;
  msg.msg_name = &from;
  msg.msg_namelen = sizeof(from);
  msg.msg_iov = &daemon->outpacket;
  msg.msg_iovlen = 1;

  if ((sz = recv_dhcp_packet(daemon->icmp6fd, &msg)) == -1 || sz < 8)
    return;

  packet = (unsigned char *)daemon->outpacket.iov_base;

  for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
    if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
      {
    union {
      unsigned char *c;
      struct in6_pktinfo *p;
    } p;
    p.c = CMSG_DATA(cmptr);

    if_index = p.p->ipi6_ifindex;
      }

  if (!indextoname(daemon->icmp6fd, if_index, interface))
    return;

  if (!iface_check(AF_LOCAL, NULL, interface, NULL))
    return;

  for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
    if (tmp->name && wildcard_match(tmp->name, interface))
      return;

  if (packet[1] != 0)
    return;

  if (packet[0] == ICMP6_ECHO_REPLY)
    lease_ping_reply(&from.sin6_addr, packet, interface); 
  else if (packet[0] == ND_ROUTER_SOLICIT)
    {
      char *mac = "";
      struct dhcp_bridge *bridge, *alias;

      /* look for link-layer address option for logging */
      if (sz >= 16 && packet[8] == ICMP6_OPT_SOURCE_MAC && (packet[9] * 8) + 8 <= sz)
    {
      print_mac(daemon->namebuff, &packet[10], (packet[9] * 8) - 2);
      mac = daemon->namebuff;
    }

Key Code Path:

main() → icmp6_packet(dnsmasq.c) → print_mac(radv.c) → sprintf(util.c)

“Source-to-sink taint analysis for this vulnerability has already been performed by Google.”

To execute the poc

Binary Analysis with Panopticon

We then took a blind analysis approach using Panopticon, focusing purely on the binary. Our steps:

  • Decompile the binary (HLIL)

  • Enumerate and identify vulnerable sinks

  • Trace execution paths to sinks

  • Check for user-controlled data flow (taint tracking)

JSON Output from Panopticon

{
  "cve_id": "CVE-2017-14492",
  "binary": "dnsmasq",
  "version": "2.75",
  "analysis_timestamp": "2025-04-11T16:16:58.214823Z",
  "entry_function": "icmp6_packet",
  "vulnerable_function": "print_mac",
  "call_chain": [
    "icmp6_packet",
    "print_mac",
    "sprintf"
  ],
  "hlil_paths": {
    "print_mac": {
      "path": "hlil_dump/print_mac.hlil.txt",
      "inline": [
        "0x11fe9: if (arg3 == 0)",
        "0x1207a: __builtin_strncpy(arg1, \"<null>\", 7)",
        "0x11fef: if (arg3 s> 0)",
        "0x11ff1: int64_t rax_1 = sx.q(arg3)",
        "0x11ffd: int64_t rbx_1 = 0",
        "0x12011: char* s = arg1",
        "0x12023: char const* const r9_1 = &data_40995[0x1a]",
        "0x12030: if (arg3 - 1 != rbx_1.d)",
        "0x12030: r9_1 = \":\"",
        "0x12034: uint64_t r8_1 = zx.q(*(arg2 + rbx_1))",
        "0x12047: rbx_1 += 1",
        "0x12052: s = &s[sx.q(__sprintf_chk(s, 1, -1, \"%.2x%s\", r8_1, r9_1, rax_1, arg1))]",
        "0x12059: do while (rbx_1 != rax_1)",
        "0x1206e: return arg1"
      ]
    },
    "icmp6_packet": {
      "path": "hlil_dump/icmp6_packet.hlil.txt",
      "inline": [
        "0x3a928: void* fsbase",
        "0x3a928: int64_t rax = *(fsbase + 0x28)",
        "0x3a943: void* var_c0 = &nullptr->section_header_offset",
        "0x3a94c: void var_88",
        "0x3a94c: void* i_2 = &var_88",
        "0x3a956: void var_a8",
        "0x3a956: void* var_e8 = &var_a8",
        "0x3a95a: uint64_t dnsmasq_daemon_1 = dnsmasq_daemon",
        "0x3a961: int32_t var_b8 = 0",
        "0x3a976: int32_t var_e0 = 0x1c",
        "0x3a97e: int64_t var_d8 = dnsmasq_daemon_1 + 0x670",
        "0x3a983: int64_t var_d0 = 1",
        "0x3a98c: ssize_t rax_1 = recv_dhcp_packet(*(dnsmasq_daemon_1 + 0x684), &var_e8)",
        "0x3a995: if (rax_1 s> 7)",
        "0x3a99b: uint64_t dnsmasq_daemon_3 = dnsmasq_daemon",
        "0x3a9aa: int32_t r12_1 = 0",
        "0x3a9ad: char* r13_1 = *(dnsmasq_daemon_3 + 0x670)",
        "0x3a9b8: if (var_c0 u> 0xf)",
        "0x3a9ba: void* i = i_2",
        "0x3a9bf: void* rcx_2 = var_c0 + i",
        "0x3a9fb: while (i != 0)",
        "0x3aa0c: int64_t rdx_1",
        "0x3aa0c: if (*(i + 8) != 0x29 || *(i + 0xc) != *(dnsmasq_daemon_3 + 0x5e8))",
        "0x3a9c8: rdx_1 = *i",
        "0x3a9cf: if (rdx_1 u<= 0xf)",
        "0x3a9cf: break",
        "0x3aa0e: rdx_1 = *i",
        "0x3aa11: r12_1 = *(i + 0x20)",
        "0x3aa19: if (rdx_1 u<= 0xf)",
        "0x3aa19: break",
        "0x3a9d9: i += (rdx_1 + 7) & 0xfffffffffffffff8",
        "0x3a9e3: if (rcx_2 u< i + 0x10)",
        "0x3a9e3: break",
        "0x3a9f6: if (rcx_2 u< ((*i + 7) & 0xfffffffffffffff8) + i)",
        "0x3a9f6: break",
        "0x3aa3b: void var_58",
        "0x3aa3b: if (indextoname(zx.q(*(dnsmasq_daemon_3 + 0x684)), r12_1, &var_58) != 0 && iface_check(1, nullptr, &var_58, nullptr) != 0)",
        "0x3aa8c: int64_t* i_1 = *(dnsmasq_daemon + 0x140)",
        "0x3aa96: while (i_1 != 0)",
        "0x3aaa0: char* rdi_4 = *i_1",
        "0x3aaa6: if (rdi_4 != 0 && wildcard_match(rdi_4, &var_58) != 0)",
        "0x3aab2: goto label_3aa45",
        "0x3aab4: i_1 = i_1[5]",
        "0x3aac2: if (r13_1[1] == 0)",
        "0x3aac8: char rax_8 = *r13_1",
        "0x3aacf: int32_t var_a0",
        "0x3aacf: if (rax_8 == 0x81)",
        "0x3abb4: lease_ping_reply(&var_a0, r13_1, &var_58)",
        "0x3aad7: if (rax_8 == 0x85)",
        "0x3aadd: uint64_t dnsmasq_daemon_2 = dnsmasq_daemon",
        "0x3aaf6: if (rax_1 s> 0xf && r13_1[8] == 1)",
        "0x3abe5: int32_t rdx_14 = (&(&nullptr->ident.abi_version)[zx.q(r13_1[9]) << 3]).d",
        "0x3abf2: if (sx.q(rdx_14) s<= rax_1)",
        "0x3ac06: print_mac(*(dnsmasq_daemon_2 + 0x330), &r13_1[0xa], rdx_14 - 0xa)",
        "0x3ac0b: dnsmasq_daemon_2 = dnsmasq_daemon",
        "0x3ac12: *(dnsmasq_daemon_2 + 0x330)",
        "0x3ab00: if ((*(dnsmasq_daemon_2 + 5) & 0x10) == 0)",
        "0x3abcf: my_syslog(0x1e, \"RTR-SOLICIT(%s) %s\", 0)",
        "0x3abd4: dnsmasq_daemon_2 = dnsmasq_daemon",
        "0x3ab06: char* ifname = *(dnsmasq_daemon_2 + 0x658)",
        "0x3ab10: if (ifname != 0)",
        "0x3ab24: while (true)",
        "0x3ab24: uint32_t rax_9 = if_nametoindex(ifname)",
        "0x3ab2e: if (rax_9 != 0)",
        "0x3ab30: char* rbx_2 = *(ifname + 0x10)",
        "0x3ab37: if (rbx_2 != 0)",
        "0x3ab54: while (true)",
        "0x3ab5b: if (wildcard_matchn(rbx_2, &var_58, 0x10) != 0)",
        "0x3ab6b: send_ra_alias(arg1, rax_9, ifname, nullptr, r12_1)",
        "0x3ab70: break",
        "0x3ab40: rbx_2 = *(rbx_2 + 0x18)",
        "0x3ab47: if (rbx_2 == 0)",
        "0x3ab47: goto label_3ab18",
        "0x3ab5b: break",
        "0x3ab18: label_3ab18:",
        "0x3ab18: ifname = *(ifname + 0x18)",
        "0x3ab1f: if (ifname == 0)",
        "0x3ab1f: goto label_3ab79",
        "0x3ab79: label_3ab79:",
        "0x3ab79: int32_t* rcx_4 = &var_a0",
        "0x3ab95: int32_t var_9c",
        "0x3ab95: int32_t var_98",
        "0x3ab95: int32_t var_94",
        "0x3ab95: if ((var_a0 | var_9c | var_98 | var_94) == 0)",
        "0x3ab95: rcx_4 = nullptr",
        "0x3ab9f: send_ra_alias(arg1, r12_1, &var_58, rcx_4, r12_1)",
        "0x3aa45: label_3aa45:",
        "0x3aa4e: if (rax == *(fsbase + 0x28))",
        "0x3aa65: return rax - *(fsbase + 0x28)",
        "0x3ac1e: __stack_chk_fail()",
        "0x3ac1e: noreturn"
      ]
    }
  },
  "sink_calls": [],
  "arg3_expression": "rdx_14 - 0xa",
  "taint_trace": [
    {
      "source": "hlil_sub",
      "expression": "rdx_14 - 0xa",
      "value": null,
      "location": "0x3ac06"
    }
  ],
  "evidence": [
    "`icmp6_packet()` directly calls `print_mac()`.",
    "Working PoC confirmed triggers heap overflow reliably."
  ],
  "exploitability": "confirmed",
  "confidence_score": 10,
  "recommendation": "Use snprintf() with safe bounds and validate arg3",
  "poc_details": {
    "file": "poc.py",
    "sends": "ICMPv6 Router Solicitation",
    "sets_option_type": 1,
    "sets_length": 255,
    "payload_bytes": 2040
  }
}

Conclusion

Our analysis confirmed that CVE-2017-14492 is reachable and exploitable from within the binary, validating the usefulness of Panopticon for deep firmware triage.

Important Note:
In this case, the customer’s device had IPv6 disabled at the kernel and driver level, preventing the exploit from succeeding in real-world conditions. However, if IPv6 were enabled, this vulnerability would have allowed remote DoS or RCE via a crafted packet.

Key Takeaways

  • Even known vulnerabilities must be analyzed for exploitability in specific environments.

  • Panopticon successfully mapped the source-to-sink path purely from binary, proving its value in firmware security triage.

  • Default services like dnsmasq pose risks when bundled in devices with broad protocol support enabled.