Skip to content

File Descriptor leak and deadlock when BIRD termination string 0000 \n crosses the 4096-byte buffer boundary #6

Description

@jbrunk1966

this issue is linked to czerwonk/bird_exporter#139

Environment
OS: Ubuntu 26.04 LTS (resolute)

Kernel: Linux 7.0.0-27-generic x86_64

BIRD version: 2.18

Exporter version: 1.4.4 (Ubuntu Repo) AND 1.5.0 (GitHub release)

Symptom
The prometheus-bird-exporter causes a massive File Descriptor (FD) leak on the BIRD unix socket, eventually leading to a complete hang of the BIRD daemon once it hits its soft limit of 1024 open files.
The exporter process shows 0% CPU usage but continuously accumulates open FDs with every Prometheus scrape.

Root Cause Analysis (Buffer Boundary Bug)
After extensive debugging using strace, lsof, and curl, we identified the root cause as a buffer boundary parsing issue.

The exporter reads the BIRD socket in fixed chunks of 4096 bytes. BIRD terminates its responses (e.g., to show protocols all) with the string 0000 \n.
If the total byte length of BIRD's output happens to align perfectly with the buffer size (e.g., around 8192 bytes), the termination string is split across two read operations.

Here is the actual strace output of the exporter hitting the bug:

Code-Snippet
[pid 302649] read(56, "2002-Name...", 4096) = 4096 <-- Block 1
[pid 302649] read(56, "...", 4096) = 4096 <-- Block 2 (ends exactly after the first two zeros of the termination string)
[pid 302649] read(56, "00 \n", 4096) = 4 <-- Block 3 (receives the split remainder)
The Chain of Events:

The exporter receives 00 at the end of block 2, and 00 \n in block 3.

Because the termination string 0000 \n is never read as a continuous sequence in a single buffer, the exporter fails to recognize the end of the transmission.

The exporter calls epoll_pwait and waits indefinitely for more data from BIRD (causing the 0% CPU deadlock).

After a few seconds, the HTTP client (Prometheus) times out and drops the connection.

The exporter aborts the scrape but fails to close the Unix socket to BIRD, resulting in an FD leak on both sides (Exporter and BIRD).

Steps to Reproduce
This is highly dependent on the exact length of the BIRD output. In our case, the bug was triggered dynamically by BIRD's internal Since timestamp formatting:

When BIRD protocols run for >24h, the timestamp is short (2026-06-30).

When BIRD is freshly restarted, the timestamp includes milliseconds (05:21:23.226).

This formatting difference added exactly enough bytes to push the termination string precisely onto the 8192-byte boundary (2x 4096 bytes), immediately triggering the deadlock on every scrape.

Workaround
We verified this bug by artificially padding a description field in the bird.conf by a few characters (e.g., adding FIX_BUFFER_DEADLOCK_123). This shifted the BIRD output length away from the 4096-byte boundary. After a birdc configure, the exporter read the termination string in one piece, closed the sockets correctly, and the FD leak disappeared completely.

Suggested Fix
The socket reading logic should use a buffered reader (like bufio.Reader or bufio.Scanner) that safely searches for the delimiter across chunk boundaries, or properly inspect the combined buffer rather than relying on the termination string appearing completely within a single raw 4096-byte chunk. Additionally, a timeout context should ensure that the unix socket is closed (defer conn.Close()) even if the parsing hangs or the HTTP client disconnects.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions