Skip to content
Security Article

Anatomy of a 27-Year-Old OpenBSD Authentication Bypass

A simple validation omission in OpenBSD's PPP stack shows how easily decoupled memory bounds can compromise system security.

Ji-ho Choi
Ji-ho Choi
Security & Cloud Editor · Jun 17, 2026 · 5 min read
Anatomy of a 27-Year-Old OpenBSD Authentication Bypass

Legacy codebases often harbor subtle logical flaws that survive major architectural shifts. A striking example recently surfaced in OpenBSD's sppp(4) subsystem, which handles synchronous Point-to-Point Protocol (PPP) links. A one-line authentication bypass in the Password Authentication Protocol (PAP) code had been present since the code was first imported in July 1999. By exploiting this flaw, an attacker could bypass authentication entirely or trigger a kernel heap over-read.

The Mechanics of the Zero-Length Bypass

The vulnerability resides in the sppp_pap_input() function within sys/net/if_spppsubr.c. When an OpenBSD system acts as a PAP authenticator, it processes incoming credentials and compares the peer-provided username and password against the configured local values.

The original implementation used the standard C library function bcmp to perform this comparison:

if (name_len > AUTHMAXLEN || passwd_len > AUTHMAXLEN ||
    bcmp(name, sp->hisauth.name, name_len) != 0 ||
    bcmp(passwd, sp->hisauth.secret, passwd_len) != 0) {
    /* authentication failed */
}

The fundamental security flaw here is that name_len and passwd_len are parsed directly from the incoming PAP frame, meaning they are entirely controlled by the remote peer.

While the code checks that these lengths do not exceed AUTHMAXLEN (which was updated to 256 in later revisions), it fails to enforce a lower bound or verify that the received length matches the actual length of the stored credentials. In C, calling bcmp(buf, ref, 0) compares zero bytes and immediately returns 0, indicating a successful match.

Consequently, if an attacker transmits a PAP authentication request with both name_len and passwd_len set to 0, both bcmp operations evaluate to 0. The failure branch is bypassed, the system issues a PAP_ACK, and the connection is authenticated without any credentials.

Decoupled Bounds and the Kernel Heap Over-Read

The vulnerability's history reveals how subsequent code modifications can exacerbate an existing logical flaw. The vulnerable sppp code was originally imported into OpenBSD from FreeBSD on July 1, 1999, tracing its origins back to a mid-1990s implementation by Cronyx Engineering Ltd.

Initially, the risk was limited to the authentication bypass. The credentials were stored in fixed-size arrays within the system structures, and the lengths were validated against AUTHNAMELEN (64) and AUTHKEYLEN (16).

However, on February 16, 2009, a commit aimed at supporting longer credentials changed the storage mechanism. The fixed-size arrays were replaced with dynamically allocated buffers using malloc(strlen(configured_string) + 1). To accommodate this, the separate bounds checks were replaced with a single AUTHMAXLEN check of 256.

This architectural change decoupled the memory allocation size from the validation bound. If an attacker sends a username length (name_len) of 200, but the configured username is only 8 bytes long, the name_len > AUTHMAXLEN check passes. The system then executes bcmp(name, sp->hisauth.name, name_len). Because name_len (200) exceeds the allocated size of the configured credential (9 bytes), bcmp reads past the bounds of the heap-allocated buffer. This results in a kernel heap over-read, leaking up to 192 bytes of adjacent kernel memory back to the comparison context.

Ironically, the Challenge-Handshake Authentication Protocol (CHAP) handler in the very same file was immune to this issue. It employed an explicit length verification before performing the memory comparison:

if (name_len != strlen(sp->hisauth.name) || bcmp(name, sp->hisauth.name, name_len) != 0) {

The PAP handler, however, did not receive a similar safety check, allowing the vulnerability to persist for nearly three decades.

Protocol-Level Reachability and Exploitation

The impact of this vulnerability is highly practical because the code path is directly reachable via the PPPoE (Point-to-Point Protocol over Ethernet) data path. The execution flow progresses from the network interface driver through pppoe_data_input, pppoeintr, sppp_input, and finally into sppp_pap_input.

An attacker situated within the same broadcast domain can set up a rogue PPPoE server. When an OpenBSD client attempts to establish a connection, the rogue server can negotiate the Link Control Protocol (LCP), initiate PAP authentication, and send an empty PAP authentication request.

Because the zero-length credentials bypass the authentication check, the OpenBSD client accepts the authentication, completes the Internet Protocol Control Protocol (IPCP) negotiation, and establishes a fully operational network layer link. At this point, the victim's system routes its IP traffic directly through the attacker's rogue server, enabling man-in-the-middle interception.

Security researchers confirmed this behavior on OpenBSD 7.6 running within a QEMU/KVM environment, demonstrating that the entire handshake—from discovery to active ICMP routing—succeeds without valid credentials.

The Remediation and Developer Lessons

The vulnerability was reported to OpenBSD developers on June 12, 2026, and a fix was committed on June 14, 2026, by developer mvs in commit openbsd/src@076e2b1.

The remediation aligns the PAP credential verification with the robust logic used by the CHAP handler. It introduces explicit length checks to ensure that the incoming credential lengths exactly match the lengths of the configured strings:

if (name_len != strlen(sp->hisauth.name) ||
    passwd_len != strlen(sp->hisauth.secret) ||
    bcmp(name, sp->hisauth.name, name_len) != 0 ||
    bcmp(passwd, sp->hisauth.secret, passwd_len) != 0) {

This simple addition resolves both security issues simultaneously:

  1. It prevents the zero-length bypass because 0 will not match the length of a non-empty configured credential.
  2. It bounds the bcmp operation to the exact size of the allocated memory, eliminating the possibility of a kernel heap over-read.

For developers, this 27-year-old bug offers critical lessons in API safety and defensive programming:

  • Never trust peer-provided lengths: When comparing buffers, the length parameter must be validated against the actual allocated size of both buffers, not just an arbitrary maximum limit.
  • Avoid decoupling allocation from validation: When refactoring static structures to dynamic memory, ensure that validation logic is updated to reflect the dynamic boundaries of the new allocations.
  • Audit similar code paths: The presence of secure logic in the CHAP handler alongside insecure logic in the PAP handler highlights the necessity of cross-referencing similar protocol implementations during security audits.

Sources & further reading

  1. A 27-Year-Old Authentication Bypass in OpenBSD's PPP Stack — blog.argus-systems.ai
Ji-ho Choi
Written by
Ji-ho Choi · Security & Cloud Editor

Ji-ho covers the increasingly tangled overlap between cloud architecture and security, drawing on a background as a penetration tester to keep his reporting grounded in real-world attack paths. He never lets a vendor claim go unquestioned and insists that every buzzword come with a proof of concept.

Discussion 0

Join the discussion

Sign in or create an account to comment and vote.

No comments yet

Be the first to weigh in.

Related Reading