Public bug reported:

# libevent: backport evdns server NULL-deref crash fix to 2.1.12-stable

**Summary:** `libevent`'s DNS *server* code (`evdns.c`, `request_parse()`) 
dereferences
an uninitialized `NULL` pointer when it receives a UDP datagram shorter than the
12-byte DNS header, crashing (SIGSEGV) any application that uses libevent's 
evdns
server facility. This is upstream bug [libevent#1231][1], **already fixed on
`master`** by commit `d076d2df` but **never released in a stable tarball**, so 
the
distro's `2.1.12-stable` package is still affected. Please backport the one-line
fix.

---

## Package

| | |
|---|---|
| Source package | `libevent` |
| Version affected | `2.1.12-stable-10ubuntu0.1` (Ubuntu 26.04.1 LTS 
"resolute") |
| Binary packages | `libevent-2.1-7t64`, `libevent-core-2.1-7t64` (amd64) |
| Also affects | Debian's `libevent` `2.1.12-stable-*`, and every distro 
shipping the 2.1.12 stable release (upstream 2020-07-05) |
| Fixed upstream in | commit `d076d2df843bc5fb` on `master` (2021-11-30); not 
in any released tarball |

## Impact

Denial of service: a single malformed UDP datagram crashes the process. Any 
program
that opens a libevent evdns **server** port is affected. The most widely 
deployed
example is **Tor**, whose `DNSPort` uses this API — a crafted short packet to 
the
DNSPort takes Tor down, and under systemd it repeatedly trips the service 
restart
limiter.

Reachability depends on the application's binding:

- Normally the evdns server port is bound to loopback, so this is a **local 
DoS**
  (any local process can crash the service).
- In transparent-proxy / DNS-redirect setups (e.g. Tor as a system-wide 
transparent
  proxy, where firewall rules redirect *all* UDP port 53 into the DNSPort), 
**any
  local process — and any LAN host in gateway configurations — can crash the 
daemon**
  by sending fewer than 12 bytes to port 53.

No CVE is assigned to upstream issue #1231 as far as I can find; it is 
nonetheless a
straightforward remotely/locally triggerable crash and I'd suggest treating it 
as a
security-relevant DoS.

## Root cause

In `evdns.c`, the server-side `request_parse()`:

```c
static int
request_parse(u8 *packet, int length, struct evdns_server_port *port, ...)
{
    ...
    struct server_request *server_req = NULL;   /* starts NULL */

    /* Read the 12-byte DNS header. Each GET16 does: if (j+2 > length) goto 
err; */
    GET16(trans_id);
    GET16(flags);
    GET16(questions);
    GET16(answers);
    GET16(authority);
    GET16(additional);
    ...
    /* server_req is allocated only AFTER the whole header has parsed: */
    server_req = mm_malloc(sizeof(struct server_request));
    ...
err:
    if (server_req->base.questions) {   /* <-- NULL deref: server_req is still 
NULL */
        for (i = 0; i < server_req->base.nquestions; ++i)
            mm_free(server_req->base.questions[i]);
        mm_free(server_req->base.questions);
    }
    mm_free(server_req);
    return -1;
}
```

If the datagram is shorter than 12 bytes, one of the header `GET16` macros 
jumps to
`err:` **before** `server_req` has been allocated, so `server_req` is still 
`NULL`.
The cleanup then reads `server_req->base.questions`. `base` is the last member 
of
`struct server_request`, sitting after a 128-byte `sockaddr_storage`, so the 
access
lands at offset ~`0xe0` — i.e. a read of address `0x00000000000000e0`, which 
faults.

This missing `NULL` guard was introduced by commit `991f0ed3` ("evdns: do not 
check
server_req twice", a bad assumption that `server_req` can't be `NULL` at 
`err:`),
which shipped in `2.1.12-stable`. It was reverted upstream in `d076d2df`.

## The fix (already upstream — please cherry-pick)

Upstream commit `d076d2df843bc5fb` — *Revert "evdns: do not check server_req 
twice"*
(reverts `991f0ed3`, "Fixes: #1231") — restores the `NULL` guard:

```diff
 err:
-       if (server_req->base.questions) {
-               for (i = 0; i < server_req->base.nquestions; ++i)
-                       mm_free(server_req->base.questions[i]);
-               mm_free(server_req->base.questions);
+       if (server_req) {
+               if (server_req->base.questions) {
+                       for (i = 0; i < server_req->base.nquestions; ++i)
+                               mm_free(server_req->base.questions[i]);
+                       mm_free(server_req->base.questions);
+               }
+               mm_free(server_req);
        }
-       mm_free(server_req);
        return -1;
```

Request: add this as a distro patch (a debian/patches quilt patch cherry-picking
`d076d2df`) to the `2.1.12-stable-10ubuntu*` / Debian `2.1.12-stable-*` package.

## Steps to reproduce

Tested on Ubuntu 26.04.1 LTS, `libevent 2.1.12-stable-10ubuntu0.1`, using Tor
(`0.4.9.11-0ubuntu0.26.04.1`) as a convenient evdns-server host.

1. Run a Tor instance with a DNSPort, e.g. a minimal `torrc`:

   ```
   DataDirectory /tmp/torrepro
   SocksPort 0
   DNSPort 127.0.0.1:15353
   Log notice stderr
   ```
   `tor -f /tmp/torrepro/torrc` (wait until `127.0.0.1:15353` is listening).

2. Send a datagram shorter than the 12-byte DNS header:

   ```
   bash -c 'printf "\x00\x00" > /dev/udp/127.0.0.1/15353'
   ```

3. Tor dies with `Caught signal 11` (SIGSEGV). Any length from 2 to 11 bytes
   reproduces reliably; a well-formed >=12-byte header does not.

Under gdb the fault is `SIGSEGV` at `si_addr = 0xe0`, with the crashing frame 
inside
`libevent-2.1.so.7` (offset `+0xcd7d`), called from `event_base_loop` — i.e. the
evdns server read path, not the host application.

### Minimal libevent-only reproducer (no Tor)

For a repro that exercises libevent directly (compile with
`cc repro.c -levent -o repro`, needs `libevent-dev`):

```c
#include <event2/event.h>
#include <event2/dns.h>
#include <event2/util.h>
#include <netinet/in.h>
#include <string.h>

static void cb(struct evdns_server_request *req, void *d) {
    (void)d; evdns_server_request_respond(req, 0);
}

int main(void) {
    struct event_base *base = event_base_new();
    evutil_socket_t s = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in sin;
    memset(&sin, 0, sizeof sin);
    sin.sin_family = AF_INET;
    sin.sin_port = htons(15353);
    sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    bind(s, (struct sockaddr *)&sin, sizeof sin);
    evutil_make_socket_nonblocking(s);
    evdns_add_server_port_with_base(base, s, 0, cb, NULL);
    event_base_dispatch(base);
    return 0;
}
```
Then `bash -c 'printf "\x00\x00" > /dev/udp/127.0.0.1/15353'` crashes it 
identically.

## References

- Upstream issue: libevent#1231 — "evdns crashes in parse_request on malformed 
DNS packets" (closed) — https://github.com/libevent/libevent/issues/1231
- Fixing commit: `d076d2df843bc5fb` — Revert "evdns: do not check server_req 
twice" — https://github.com/libevent/libevent/commit/d076d2df843bc5fb
- Regression-introducing commit: `991f0ed3d86ffca0c017ab83cd239289912bdaad`

---

### How to file

**Ubuntu (Launchpad):** run `ubuntu-bug libevent-2.1-7t64` on the affected 
machine
(attaches package/version automatically), or file at
https://bugs.launchpad.net/ubuntu/+source/libevent/+filebug — paste the sections
above. Tag `patch` and mention it's a straightforward cherry-pick of an 
upstream fix.

**Debian:** `reportbug --severity=important libevent-2.1-7t64` (submits to
[email protected]), or email a report to that address. Consider tagging
`security` given it is a triggerable crash.

[1]: https://github.com/libevent/libevent/issues/1231
➜  alltor ubuntu-bug libevent-2.1-7t64
REDACTED config part /etc/cloud/cloud.cfg.d/99-installer.cfg, insufficient 
permissions
REDACTED config part 
/etc/cloud/cloud.cfg.d/00-subiquity-disable-cloudinit-networking.cfg, 
insufficient permissions
[49191] Sandbox: CanCreateUserNamespace() unshare(CLONE_NEWPID): EPERM
➜  alltor ls
README.md  USER_GUIDE.md  alltor  alltor-test  alltor-test.bak  alltor.bak  
libevent-evdns-backport-bug.md  tor
➜  alltor cat libevent-evdns-backport-bug.md 
# libevent: backport evdns server NULL-deref crash fix to 2.1.12-stable

**Summary:** `libevent`'s DNS *server* code (`evdns.c`, `request_parse()`) 
dereferences
an uninitialized `NULL` pointer when it receives a UDP datagram shorter than the
12-byte DNS header, crashing (SIGSEGV) any application that uses libevent's 
evdns
server facility. This is upstream bug [libevent#1231][1], **already fixed on
`master`** by commit `d076d2df` but **never released in a stable tarball**, so 
the
distro's `2.1.12-stable` package is still affected. Please backport the one-line
fix.

---

## Package

| | |
|---|---|
| Source package | `libevent` |
| Version affected | `2.1.12-stable-10ubuntu0.1` (Ubuntu 26.04.1 LTS 
"resolute") |
| Binary packages | `libevent-2.1-7t64`, `libevent-core-2.1-7t64` (amd64) |
| Also affects | Debian's `libevent` `2.1.12-stable-*`, and every distro 
shipping the 2.1.12 stable release (upstream 2020-07-05) |
| Fixed upstream in | commit `d076d2df843bc5fb` on `master` (2021-11-30); not 
in any released tarball |

## Impact

Denial of service: a single malformed UDP datagram crashes the process. Any 
program
that opens a libevent evdns **server** port is affected. The most widely 
deployed
example is **Tor**, whose `DNSPort` uses this API — a crafted short packet to 
the
DNSPort takes Tor down, and under systemd it repeatedly trips the service 
restart
limiter.

Reachability depends on the application's binding:

- Normally the evdns server port is bound to loopback, so this is a **local 
DoS**
  (any local process can crash the service).
- In transparent-proxy / DNS-redirect setups (e.g. Tor as a system-wide 
transparent
  proxy, where firewall rules redirect *all* UDP port 53 into the DNSPort), 
**any
  local process — and any LAN host in gateway configurations — can crash the 
daemon**
  by sending fewer than 12 bytes to port 53.

No CVE is assigned to upstream issue #1231 as far as I can find; it is 
nonetheless a
straightforward remotely/locally triggerable crash and I'd suggest treating it 
as a
security-relevant DoS.

## Root cause

In `evdns.c`, the server-side `request_parse()`:

```c
static int
request_parse(u8 *packet, int length, struct evdns_server_port *port, ...)
{
    ...
    struct server_request *server_req = NULL;   /* starts NULL */

    /* Read the 12-byte DNS header. Each GET16 does: if (j+2 > length) goto 
err; */
    GET16(trans_id);
    GET16(flags);
    GET16(questions);
    GET16(answers);
    GET16(authority);
    GET16(additional);
    ...
    /* server_req is allocated only AFTER the whole header has parsed: */
    server_req = mm_malloc(sizeof(struct server_request));
    ...
err:
    if (server_req->base.questions) {   /* <-- NULL deref: server_req is still 
NULL */
        for (i = 0; i < server_req->base.nquestions; ++i)
            mm_free(server_req->base.questions[i]);
        mm_free(server_req->base.questions);
    }
    mm_free(server_req);
    return -1;
}
```

If the datagram is shorter than 12 bytes, one of the header `GET16` macros 
jumps to
`err:` **before** `server_req` has been allocated, so `server_req` is still 
`NULL`.
The cleanup then reads `server_req->base.questions`. `base` is the last member 
of
`struct server_request`, sitting after a 128-byte `sockaddr_storage`, so the 
access
lands at offset ~`0xe0` — i.e. a read of address `0x00000000000000e0`, which 
faults.

This missing `NULL` guard was introduced by commit `991f0ed3` ("evdns: do not 
check
server_req twice", a bad assumption that `server_req` can't be `NULL` at 
`err:`),
which shipped in `2.1.12-stable`. It was reverted upstream in `d076d2df`.

## The fix (already upstream — please cherry-pick)

Upstream commit `d076d2df843bc5fb` — *Revert "evdns: do not check server_req 
twice"*
(reverts `991f0ed3`, "Fixes: #1231") — restores the `NULL` guard:

```diff
 err:
-       if (server_req->base.questions) {
-               for (i = 0; i < server_req->base.nquestions; ++i)
-                       mm_free(server_req->base.questions[i]);
-               mm_free(server_req->base.questions);
+       if (server_req) {
+               if (server_req->base.questions) {
+                       for (i = 0; i < server_req->base.nquestions; ++i)
+                               mm_free(server_req->base.questions[i]);
+                       mm_free(server_req->base.questions);
+               }
+               mm_free(server_req);
        }
-       mm_free(server_req);
        return -1;
```

Request: add this as a distro patch (a debian/patches quilt patch cherry-picking
`d076d2df`) to the `2.1.12-stable-10ubuntu*` / Debian `2.1.12-stable-*` package.

## Steps to reproduce

Tested on Ubuntu 26.04.1 LTS, `libevent 2.1.12-stable-10ubuntu0.1`, using Tor
(`0.4.9.11-0ubuntu0.26.04.1`) as a convenient evdns-server host.

1. Run a Tor instance with a DNSPort, e.g. a minimal `torrc`:

   ```
   DataDirectory /tmp/torrepro
   SocksPort 0
   DNSPort 127.0.0.1:15353
   Log notice stderr
   ```
   `tor -f /tmp/torrepro/torrc` (wait until `127.0.0.1:15353` is listening).

2. Send a datagram shorter than the 12-byte DNS header:

   ```
   bash -c 'printf "\x00\x00" > /dev/udp/127.0.0.1/15353'
   ```

3. Tor dies with `Caught signal 11` (SIGSEGV). Any length from 2 to 11 bytes
   reproduces reliably; a well-formed >=12-byte header does not.

Under gdb the fault is `SIGSEGV` at `si_addr = 0xe0`, with the crashing frame 
inside
`libevent-2.1.so.7` (offset `+0xcd7d`), called from `event_base_loop` — i.e. the
evdns server read path, not the host application.

### Minimal libevent-only reproducer (no Tor)

For a repro that exercises libevent directly (compile with
`cc repro.c -levent -o repro`, needs `libevent-dev`):

```c
#include <event2/event.h>
#include <event2/dns.h>
#include <event2/util.h>
#include <netinet/in.h>
#include <string.h>

static void cb(struct evdns_server_request *req, void *d) {
    (void)d; evdns_server_request_respond(req, 0);
}

int main(void) {
    struct event_base *base = event_base_new();
    evutil_socket_t s = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in sin;
    memset(&sin, 0, sizeof sin);
    sin.sin_family = AF_INET;
    sin.sin_port = htons(15353);
    sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    bind(s, (struct sockaddr *)&sin, sizeof sin);
    evutil_make_socket_nonblocking(s);
    evdns_add_server_port_with_base(base, s, 0, cb, NULL);
    event_base_dispatch(base);
    return 0;
}
```
Then `bash -c 'printf "\x00\x00" > /dev/udp/127.0.0.1/15353'` crashes it 
identically.

## References

- Upstream issue: libevent#1231 — "evdns crashes in parse_request on malformed 
DNS packets" (closed) — https://github.com/libevent/libevent/issues/1231
- Fixing commit: `d076d2df843bc5fb` — Revert "evdns: do not check server_req 
twice" — https://github.com/libevent/libevent/commit/d076d2df843bc5fb
- Regression-introducing commit: `991f0ed3d86ffca0c017ab83cd239289912bdaad`

ProblemType: Bug
DistroRelease: Ubuntu 26.04
Package: libevent-2.1-7t64 2.1.12-stable-10ubuntu0.1
ProcVersionSignature: Ubuntu 7.0.0-31.31-generic 7.0.14
Uname: Linux 7.0.0-31-generic x86_64
ApportVersion: 2.34.1-0ubuntu0.1
Architecture: amd64
CasperMD5CheckResult: pass
CurrentDesktop: ubuntu:GNOME
Date: Thu Sep 10 10:31:20 2026
InstallationDate: Installed on 2026-08-03 (38 days ago)
InstallationMedia: Ubuntu 26.04 "Resolute Raccoon" - Release amd64 (20260423.1)
ProcEnviron:
 LANG=en_US.UTF-8
 PATH=(custom, no user)
 SHELL=/usr/bin/zsh
 TERM=xterm-256color
 XDG_RUNTIME_DIR=<set>
SourcePackage: libevent
UpgradeStatus: No upgrade log present (probably fresh install)

** Affects: libevent (Ubuntu)
     Importance: Undecided
         Status: New


** Tags: amd64 apport-bug resolute wayland-session

-- 
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2166986

Title:
  evdns server crash

To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/libevent/+bug/2166986/+subscriptions


-- 
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs

Reply via email to