Vulnerability Research

Libreswan CVE-2026-12413 — A Reachable Full-Table Assertion in IKEv2 Reassembly

CVE-2026-12413 lets an IKEv2 initiator that has not completed identity authentication fill all 30 payload descriptors in a protected fragmented message, after which Libreswan 4.6 through 5.3 mistakes the valid count for a broken invariant and terminates pluto; a remote actor can repeat the exchange to disrupt the VPN control plane, and version 5.3.1 corrects the boundary.

A warm hand-drawn IKEv2 reassembly bench where sealed fragment parcels fill every digest slot, the former end stop rejects the last valid parcel, and the repaired rack accepts the complete protected set.
In this article

Research basisPinned review of Libreswan 5.3.1, the 4.5/4.6 history, fixes 5c4369af / 6088d294 , and the upstream max-payload KVM regression

SourceLibreswan advisory, pinned source, and commit history / RFC 7296 and RFC 7383 / SOSEC source review

1 The daemon exits after the thirtieth slot is filled

The patch published in late June is almost comically small: one equals sign added after a less-than operator. It does not enlarge an array, replace an allocator, or redesign the IKEv2 state machine. That character decides whether a VPN gateway treats a completely full descriptor table as a valid representation or calls abort() after receiving a network message. The entire history of CVE-2026-12413 is contained in a deceptively simple question: is 30 a legal count, or an illegal array index?

The affected process is Libreswan's IKE daemon, pluto. An initiator first completes IKE_SA_INIT and derives the encryption and integrity keys for its new IKE SA. It then sends a fragmented IKE_AUTH exchange following RFC 7383. The first fragment may contain a chain of outer payloads before the Encrypted and Authenticated Fragment payload, known in the implementation as SKF. If parsing that outer chain leaves msg_digest.digest_roof at exactly 30, the old reassembly function reaches a failing assertion.

No thirty-first write occurs at that point. Indices 0 through 29 already hold thirty payload descriptors, and digest_roof denotes the one-past-the-end position. Reassembly obtains the existing SKF descriptor through a type chain and overwrites that same slot with the reconstructed SK descriptor. The vulnerable assertion still requires the roof to be strictly below the capacity. It rejects a full table even though no code is about to index element 30.

Libreswan implements passert as a fatal invariant. The macro builds a diagnostic containing the expression and source position, passes it to a logger function marked as never returning, and that function calls the C library's abort(). The result is a process-wide SIGABRT. This is not a rejected peer, a failed tunnel, or a worker-local exception. The daemon coordinating every IKE and Child SA in that process exits, and its in-memory state disappears with it.

A service manager may launch pluto again within seconds, but a quick restart does not erase the availability impact. In-memory IKE state, negotiations in progress, DPD timers, rekey work, and policy coordination are interrupted. Kernel XFRM state can let some existing ESP traffic continue temporarily, producing a misleading period in which old tunnels pass packets while new sessions and reauthentication fail. The actor can begin a new IKE SA as soon as UDP 500 or UDP 4500 is listening and repeat the same boundary exchange.

Libreswan lists versions 4.6 through 5.3 as affected and 5.3.1 as fixed. The CNA CVSS 3.1 vector is CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H, yielding 7.5. That vector describes the source evidence well: the path is network reachable, repeatable, and available before identity authentication; it damages availability. Reassembly does not write beyond the digest table, and this path does not supply a remote-code-execution primitive.

The code and response can be stated here. In the pinned tree, PAYLIMIT and msg_digest.digest[] are defined in programs/pluto/demux.h, while the wrong comparison is in reassemble_v2_incoming_fragments() in programs/pluto/ikev2_message.c. Versions 4.6 through 5.3 need 5.3.1 or an equivalent backport, followed by a restart into the fixed binary. fragmentation=no is only a temporary option when every IKEv2 connection is proven not to need RFC 7383; ACLs and rate limits can reduce reachability but cannot replace the repair.

1.1 Protected IKE_AUTH still arrives before identity trust

The phrase “authenticated fragment” is easy to misread as “a message from an authenticated user.” IKEv2 establishes cryptographic message protection and peer identity in separate stages. RFC 7296 assigns IKE_SA_INIT the algorithm negotiation, nonces, and Diffie–Hellman exchange. Both sides then derive SK_d, integrity keys SK_ai/SK_ar, encryption keys SK_ei/SK_er, and related keying material.

Identity proof follows inside IKE_AUTH. Certificates, AUTH payloads, EAP conversations, or pre-shared-key calculations are evaluated there. Consequently, an untrusted initiator already possesses the key for its sending direction and can create an SK or SKF message whose integrity check succeeds. Message authentication says that the packet belongs to this newly negotiated IKE SA. It has not yet established that the initiator is an authorized employee, device, or site.

The triggering peer must still behave like a competent IKE implementation. It maintains SPIs and message IDs, uses the negotiated cipher and integrity algorithm, builds a coherent payload chain, assigns fragment numbers and totals, pads correctly, and computes a valid integrity check for every fragment. A modified open-source peer or protocol harness can automate those details. No credential theft, valid certificate, or successful EAP login is required.

This timing determines how an exposure inventory should be built. Start with every listener that accepts IKEv2 initiation from an untrusted network and every effective configuration that permits RFC 7383. An inventory limited to provisioned VPN accounts misses the relevant population. Public remote-access gateways, partner concentrators, cloud edges, and site-to-site endpoints reached through shared networks all process IKE_SA_INIT before they know who is calling.

1.2 One abort can become a control-plane outage

pluto coordinates IKE negotiation, Child SA creation, key renewal, dead-peer detection, and installation of kernel policies. When the invariant terminates the daemon, all of those functions lose their owner at once. Existing ESP state may survive for some time, but every SA moves toward a point where it needs user-space action. The incident can unfold gradually as lifetimes expire, making the first minute look healthier than the next ten.

Automatic restart introduces a second pressure wave. Peers reconnect, certificate chains are verified again, DNS and LDAP may be queried, RADIUS receives a burst, PKCS#11 devices perform new operations, and HA peers reconcile roles. An actor repeating the trigger can force this expensive recovery cycle again before it settles. Startup-rate limits may eventually leave the unit failed; unlimited restarts keep the system in a noisy and resource-intensive oscillation.

Monitoring only a long-lived ping through an existing ESP SA can miss the outage. Useful signals include process PID and invocation ID, service restart count, IKE_SA_INIT and IKE_AUTH success, new Child SA rate, rekey latency, total managed SAs, authentication-backend demand, and HA role changes. A synthetic probe should establish a fresh tunnel, pass traffic, and survive a rekey, not simply reuse old kernel state.

IKEv2 trust-boundary diagram: IKE_SA_INIT creates protection keys, an initiator that has not passed identity authentication sends an integrity-valid fragmented IKE_AUTH, and the message reaches payload parsing, fragment collection, and the full-table assertion.
Swipe horizontally on a narrow screen to follow key establishment, identity authentication, and reassembly
Figure 1. Integrity proves that a fragment came from the initiator of this IKE SA. Authorization is established inside IKE_AUTH later, placing the vulnerable parser between those two trust stages.

2 Why the first fragment can push the table to its edge

The trigger becomes clearer once IP fragmentation and IKE fragmentation are separated. Network-layer fragmentation divides one IP datagram, and loss of any piece prevents reconstruction. Firewalls and NAT devices frequently handle fragments inconsistently. RFC 7383 moves the split into IKE: after an IKE SA has protection keys, the sender divides a protected message into several independently authenticated SKF payloads. Each fragment is a complete IKE message that can be verified and collected by sequence number.

The extension mainly helps large IKE_AUTH exchanges carrying certificate chains. IKE_SA_INIT itself cannot use RFC 7383 because the required encryption and integrity keys do not yet exist. Peers advertise support with IKEV2_FRAGMENTATION_SUPPORTED during the initial exchange. A later protected request or response can then be divided to fit a conservative message size. Libreswan enables this facility by default when fragmentation=yes.

SKF must be the final outer payload. In fragment one, its Next Payload field identifies the first plaintext payload that will exist after reassembly. In every later fragment, that field must be NONE. RFC 7383 also preserves a less common layout: unencrypted outer payloads may precede SKF in the first fragment. Those outer elements do not appear in subsequent fragments. A receiver consequently keeps the first fragment's complete msg_digest as the anchor for the set.

Every generic payload header points to the next payload type. The decoder starts with the type in the IKE header, reads the current common header and declared length, honors the critical bit, advances a bounded input stream, and follows the chain until NONE. The actor does not need twenty-nine complicated extensions with meaningful business semantics. Structurally valid outer entries can advance the parser's accounting, although the precise accounting depends on the source branch.

2.1 RFC 7383 gives fragment one a special shape

A fragment set carries a one-based number and a total. Number zero is invalid, the number cannot exceed the total, and the total stays constant for the set. Fragment one carries a nonzero inner Next Payload; later fragments carry zero. The exchange type, message ID, SPIs, and sender role locate the set within an IKE SA. Each fragment is integrity checked before its plaintext is trusted.

Libreswan caps a set at MAX_IKE_FRAGMENTS, which is 32. The receiving structure allocates frags[MAX_IKE_FRAGMENTS + 1] because protocol numbers begin at one, and its comment explicitly leaves element zero empty. This is a deliberate one-based array representation, unlike the zero-based payload digest table at the heart of the CVE.

ignore_v2_incoming_fragment() rejects number zero, a number above the advertised total, and a total above 32. It also rejects a first fragment whose inner Next Payload is NONE and a later fragment whose field is nonzero. Existing sets enforce consistent totals and context, and duplicate positions do not increment the completed count. These checks are upstream of the vulnerable assertion.

The exploit condition consequently does not require an illegal fragment index, a total of 65,535, or an out-of-bounds fragment-array access. Two or more well-formed fragments are enough. Their integrity checks pass, their sequence is legal, and all pieces arrive. The failure appears only when the implementation combines the valid fragment set with a first-message digest whose descriptor count is exactly full.

Outer does not mean unauthenticated in the cryptographic sense. The fields preceding SKF remain covered by the IKE message integrity calculation. An on-path observer without the IKE SA key cannot alter them and retain a valid check. The malicious initiator can select those fields because it negotiated the SA and holds its sending-direction integrity key. This is an endpoint-generated attack, not packet corruption in transit.

2.2 Unknown payloads are accounted differently on two source lines

The public upstream regression uses twenty-nine unknown noncritical outer payloads followed by SKF. That construction accurately describes the relevant main-line decoder after commit 6824359f416a1eba0fabc0f19d5fe9267338bf3b. In that source shape, the loop checks capacity and then takes &md->digest[md->digest_roof++]. A payload without a specialized descriptor is still decoded through its generic header and keeps the slot that was already counted.

The 5.3.1 maintenance source has a materially different increment position. It computes pd = md->digest + md->digest_roof before choosing a descriptor. When the type is unknown, the branch reads the generic header and executes continue. The increment at the bottom of the loop does not run. Repeated unknown entries reuse the same provisional slot, while recognized entries advance the roof.

It would be inaccurate to claim that the identical twenty-nine-unknown wire sequence fills both branch shapes. The official advisory states the invariant at the correct abstraction level: payload processing can fill the digest array before fragment reassembly. On the maintenance decoder, reaching that state requires outer payloads that the branch actually records, subject to occurrence and semantic rules, with SKF ending the chain. On the main line, unknown noncritical entries offer a clean regression vehicle.

The branch distinction does not change the affected range. Once digest_roof == 30 reaches the old reassembly function, the same strict assertion fails. The parser determines how the state is built; reassembly supplies the vulnerability. Product forks may differ again, so an appliance-specific proof must be generated from its parser and not copied mechanically from a main-line KVM test.

This difference also prevents a brittle detection rule. A signature matching exactly twenty-nine unknown payloads will identify the upstream main-line regression shape and miss recognized-payload constructions or vendor changes. A stronger analytic counts dense outer payload chains before first-fragment SKF, checks their structural validity, and correlates them with a process abort. Host instrumentation can record the actual roof and remove the ambiguity.

Digest accounting across two Libreswan lines: main-line unknown payloads each consume a slot, while the 5.3.1 maintenance unknown branch continues before the increment; both implementations can ultimately reach a roof of 30 before SKF reassembly through branch-specific payload sets.
Swipe horizontally on a narrow screen to compare main-line and 5.3.1 maintenance accounting
Figure 2. The vulnerable state is “roof equals 30 at reassembly.” The route to that state varies with the parser branch, so the public unknown-payload harness is evidence for the main line, not a universal packet recipe.

3 How a thirty-slot table represents an IKE message

Libreswan does not turn a received datagram directly into a collection of long-lived protocol objects. The demultiplexer creates a struct msg_digest that accompanies the packet through parsing, IKE SA lookup, protection processing, and state-machine dispatch. It holds the IKE header, raw buffers, endpoint information, decoded payload descriptors, per-type chains, and contextual results needed by later stages.

In programs/pluto/demux.h, PAYLIMIT is 30. The message digest embeds struct payload_digest digest[PAYLIMIT] and an unsigned digest_roof. A fixed array avoids an allocation for each small payload and puts a predictable resource ceiling around untrusted messages. Each descriptor can carry a decoded header union, a bounded input stream for its body, a payload type, and a link to another descriptor of the same type.

The word roof is literal half-open-range language. Used elements occupy [0, roof). An empty table has roof zero. The first element is written at index zero and advances the roof to one. The thirtieth element is written at index 29 and advances the roof to 30. Every valid element has an index below the roof, while the roof itself may equal the number of elements in the array.

One integer therefore acts as both a count and an end iterator. It is never the last valid index. This distinction produces three related boundary expressions. A writer about to use digest[roof] requires roof < capacity. A loop over existing elements runs while its index is below roof. A pure representation check allows roof <= capacity. Confusing those questions creates an off-by-one even when every arithmetic operation is unsigned and locally plausible.

The IKEv2 decoder gets the writer condition right. At the beginning of each payload iteration it checks whether md->digest_roof >= elemsof(md->digest). If the table is full, it logs that the message has more than thirty payloads, selects v2N_INVALID_SYNTAX, and stops. A descriptor address is obtained only when space remains. Normal parsing cannot write digest[30].

The CVE sits in a different phase. Reassembly is not preparing to append a descriptor, yet the vulnerable assertion applies the writer's strict condition. A full table is a valid representation, even though it leaves no budget for decoding the protected payloads that follow. The correct response is to reassemble in place and let the next decoder report resource exhaustion through the peer-error path.

3.1 The decoder maintains order and type chains at once

ikev2_decode_payloads() receives a starting Next Payload value, a bounded input stream, a set that decides which payloads are recorded for later validation, and a logger. On each iteration it verifies global descriptor capacity, maps the numeric type to a struct_desc, parses a generic or specialized fixed header, validates the declared length, and creates a body substream whose roof cannot cross the message boundary.

The digest array preserves overall parse order. Recognized payloads are also linked into md->chain[type], with repeated payloads connected through each descriptor's next field. These are two simultaneous views of the same objects. Ordered diagnostics can walk the array, while code interested in SK, SKF, CERT, AUTH, or NOTIFY can begin at a type chain without scanning all prior entries.

Reassembly does not calculate which array position holds SKF. It reads md->chain[ISAKMP_NEXT_v2SKF]. That pointer already addresses a member of the digest table populated while fragment one was decoded. When reassembly changes the descriptor's type, it must update both chain views: remove SKF, attach SK, and replace the descriptor fields at the same address.

The descriptor budget belongs to the entire message digest, not independently to its outer and inner layers. Outer elements before SKF consume slots. After the SKF data is reconstructed as an SK container, the plaintext payloads inside SK are decoded using the same digest[] and current roof. This design keeps one bounded accounting domain across nested parsing.

If the outer layer has consumed all thirty slots, inner decoding has nowhere to record its first payload. That message is not accepted. The capacity check at the next decoder invocation produces an ordinary INVALID_SYNTAX result. The repair therefore changes process survival, not peer authorization. The actor's tunnel remains down while other peers retain service.

3.2 SKF becomes SK in place; no thirty-first slot is allocated

Once every fragment has passed integrity verification, reassemble_v2_incoming_fragments() takes another reference to the message digest saved from fragment one. It asserts that an ordinary SK chain does not already exist, asserts that SKF does exist, and expects the SKF descriptor to belong to fragment number one. Those conditions describe the object shape required for an in-place conversion.

The function's first pass sums the plaintext lengths of fragments 1 through total. Its second pass allocates a contiguous raw_packet of that size and copies each plaintext segment in order. It then prepares a local struct payload_digest sk whose input stream spans the reconstructed bytes and whose header and link fields describe an SK payload. The local structure is a value to copy, not another table member.

The conversion saves the pointer from the SKF chain, clears that chain, points the SK chain to the saved address, and executes *skf = sk. The source comment calls the final operation a scribble over SKF. The object address does not move. The digest array does not grow. No statement increments digest_roof.

Suppose SKF resides in digest[29]. After reconstruction, SK still resides in digest[29], and roof 30 still describes [0, 30). The corrected assertion, md->digest_roof <= elemsof(md->digest), admits every representable count from zero through thirty and rejects any already-corrupt value above capacity.

Every digest_roof use in the pinned tree follows the same model. IKEv1 and IKEv2 parser indexing is preceded by capacity checks, diagnostic and lifecycle loops use indices below the roof, and reassembly obtains its target through the SKF chain. No post-fix path in this source accepts equality and then indexes digest[30].

Backports deserve the same proof. Appliance vendors may have refactored descriptor ownership, added telemetry descriptors, or changed reconstruction. A clean application of the one-character diff is not enough if the surrounding operation now allocates a fresh slot. Reviewers should verify the pointer source, chain updates, assignment target, and roof behavior in the exact product tree.

In-place conversion in a full payload_digest table: indices zero through 29 are occupied and the SKF chain points to index 29; reassembly clears SKF, points SK at the same address, overwrites the descriptor with a local sk value, and leaves digest_roof at 30.
Swipe horizontally on a narrow screen to compare the array, type chains, and object address
Figure 3. Object identity supplies the decisive proof. SKF and reconstructed SK occupy one slot, making roof a count throughout reassembly and leaving every true append guarded by the original strict capacity check.

4 The complete route from a UDP message to abort()

Function names at the upper level vary across the affected history. Current code performs much of the secured dispatch near process_packet_with_secured_ike_sa(). Earlier 4.x trees coordinate the same work in ikev2_process_state_packet(). The durable sequence is outer parse, state lookup, fragment validation and collection, per-fragment integrity verification and decryption, reassembly, then inner payload decode.

collect_v2_incoming_fragment() does not cache arbitrary bytes blindly. It calls the fragment-rule validator, locates the incoming set for the message role, keeps a reference to fragment one's message digest, and records encrypted text, IV offset, and sequence information in the one-based fragment array. If the set is incomplete, state processing pauses without invoking reassembly.

When the last missing position arrives, every available fragment passes verify_and_decrypt_v2_payload(). An invalid integrity check causes rejection and cleanup; it cannot reach the full-table assertion. The attacker succeeds because an initiator completing IKE_SA_INIT legitimately knows the sending-direction key. That protocol fact is the bridge between an unauthenticated network actor and a protected reassembly routine.

The vulnerable reassembly function obtains the saved digest and checks that SK is absent, SKF is present, and the anchor corresponds to fragment one. It then evaluates the strict full-table invariant. With a roof of thirty and an array length of thirty, the expression reduces to false. Execution moves into the passert logging machinery before any plaintext-size pass or descriptor overwrite occurs.

include/passert.h declares the terminal logger path with NEVER_RETURNS. In lib/libswan/passert.c, passert_logjam_to_logger() flushes the diagnostic and calls abort(). The operating system delivers SIGABRT, the process exits, and a core may be produced. No exception boundary converts it back to an IKE notification.

4.1 A fatal invariant takes down the shared daemon

passert expresses a state developers believe cannot occur in a correct program. It differs from pexpect, a returned decoder summary, or a peer notification because it offers no recovery branch. The CVE is a reachable-assertion flaw: external input can construct the supposedly impossible state without first violating memory safety or cryptographic protection.

On a systemd installation, the journal commonly contains the assertion expression and source location, followed by a unit result such as code=dumped or status=6/ABRT. An automatic restart may follow. A useful collection records the monotonic timestamp, boot ID, invocation ID, PID, executable build ID, and restart policy. Wall-clock time alone can be misleading when several nodes or containers are involved.

After a restart, the old fragment set and IKE SA no longer exist. The actor initiates a new SA and repeats the short sequence. This requirement does not provide a meaningful defense. It adds a small amount of cryptographic work for both sides while the gateway also pays the much larger cost of process initialization and reconnecting legitimate peers.

Kernel ESP state creates a split-brain view of health. Existing policies and SAs may continue forwarding, yet no user-space process is available to negotiate a new child, handle DPD, renew keys, or adapt selectors. An organization that checks only current packet throughput may discover the control-plane loss when lifetimes expire during a later peak.

Multi-tenant appliances magnify the blast radius. The packet is associated with one new IKE SA, but the assertion belongs to the process that owns many connection profiles. Resource isolation at the connection level does not contain SIGABRT. Deployments that split tenants across processes or network namespaces may reduce impact, yet every vulnerable process remains individually triggerable.

Kubernetes may rebuild a containerized gateway and return its readiness probe to green while the IKE state remains reset. Image pulls and node scheduling can stretch one SIGABRT into a longer outage even when the replacement Pod appears healthy. Correlate Pod restartCount, IKE error rate, and external UDP traffic at the cluster layer so this sequence is not mistaken for an ordinary rolling deployment.

4.2 Adjacent boundary review found no second confirmed flaw

The adjacent boundary review covered fragment-number bounds, fragment-set allocation, total plaintext sizing, contiguous-buffer construction, and every use of digest_roof in the pinned source. The result supports the upstream impact statement: this is a reachable assertion and repeatable denial of service, with no separate out-of-bounds read, write, or integer-overflow vulnerability established in the same route.

The fragment array is intentionally sized to 33 for legal indices 1 through 32. Input validation rejects zero, rejects number above total, and rejects total above 32 before indexing. Collection and reconstruction loops use the same inclusive one-to-total range. The maximum legal index therefore addresses the last allocated member, while index zero remains unused as documented.

A received UDP packet is capped at 65,536 bytes. Even a conservative product of 32 fragments and that whole upper bound is about two mebibytes, far below a 32-bit unsigned maximum. Actual plaintext is smaller because each message contains IKE, SKF, IV, padding, and integrity overhead. The unsigned size accumulation in this constrained path does not wrap under the published limits.

Copy lengths come from plaintext chunks created by successful bounded decryption. Reassembly first sums those same lengths, allocates the resulting contiguous buffer, and then copies each chunk in sequence. We did not find a mismatch that lets the copy exceed the allocation under the validated fragment total and input-size constraints.

Duplicate fragments do not increase the completed count beyond total. Inconsistent totals and message contexts are rejected, and integrity failures remove or invalidate affected fragment content. Large numbers of half-open IKE SAs and incomplete fragment sets remain general resource-abuse concerns that a VPN service must control. They are not new vulnerabilities established by this CVE review.

The search for digest_roof showed capacity checks before parser indexing, below-roof iteration for existing descriptors, and the corrected representation assertion in reassembly. Main-line unknown-payload preservation makes the boundary easier to reach but remains under the same pre-index guard. We found no natural network path that drives the roof above thirty.

A negative adjacent result should be recorded with its scope. It means the pinned upstream tree, published limits, and reviewed functions did not yield another confirmed issue. A vendor fork that changes maximum UDP size, permits more fragments, appends a fresh SK descriptor, or uses a different numeric type must be recalculated. The review ledger preserves those assumptions for future comparison.

CVE-2026-12413 call chain: outer IKEv2 decode, fragment-rule validation, collect_v2_incoming_fragment, integrity verification and decryption, reassemble_v2_incoming_fragments, passert logging, and abort; the fixed branch proceeds to inner decoding and INVALID_SYNTAX.
Swipe horizontally on a narrow screen to follow network input, state objects, and the process exit
Figure 4. The patch changes one branch at reassembly. The old route sends a valid full representation to a fatal assertion; the fixed route performs the in-place conversion and lets ordinary syntax handling reject exhausted inner parsing.

5 Why one changed operator restores the correct semantics

Maintenance commit 5c4369afcfe95a924ee0a96a0b29a7204dc40504 and main-line commit 6088d294b8a6448df1611b98d778d85482646586 make the same logical change. The reassembly precondition becomes digest_roof <= elemsof(digest). The commit text summarizes the reasoning with a compact phrase: DIGEST_ROOF points at the roof. It names the abstraction and pinpoints the underlying semantics.

The parser's true capacity boundary remains strict. Code that is about to evaluate digest[roof] still rejects equality before taking an address. Reassembly evaluates the current representation and obtains an existing member through the SKF chain. Equality is legal for the first operation and illegal for the second. The patch aligns each condition with the operation it guards.

Deleting the assertion entirely would be weaker. A roof above 30 would indicate corrupted program state and should remain fatal or otherwise contained according to project policy. The corrected expression admits all representable values and retains an alarm for impossible ones. It is a precise invariant, not a general suppression of safety checks.

Increasing the table size would also miss the root cause. A message could fill the new size, and the same strict representation check would fail again. More capacity would increase resource use and alter behavior shared by IKEv1, diagnostics, and nested decoding. The upstream change preserves a deliberate parser budget while moving full capacity back into the normal error domain.

5.1 The fixed endpoint is a recoverable syntax failure

The main-line KVM test named cve-2026-12413-max-payloads uses impairment controls to add unknown v2 payloads to IKE_AUTH and pad the message with twenty-nine outer entries before SKF. Fragmentation forces the responder to save fragment one, collect the set, and execute the reconstruction code. The resulting roof is exactly thirty.

On a vulnerable build, the expected journey ends at SIGABRT. On the fixed build, SKF is converted to SK at the same address. Inner decode then asks for a descriptor for its first protected payload, finds no remaining budget, logs the over-capacity condition, and returns INVALID_SYNTAX. The peer reports rejection and the test tunnel remains down.

A regression specification should therefore assert process survival and attack failure separately. Record the PID, systemd invocation ID, restart counter, and core list before and after the case. Confirm the malicious connection has no established Child SA. Then establish a clean control tunnel and exercise a rekey. This catches a hidden crash-and-restart that a simple command exit code could miss.

Boundary neighbors improve confidence. A target-specific harness should produce roof 29, roof 30, and an attempted additional descriptor; ordinary unfragmented SK should remain unchanged. Fragment dimensions should include two and many pieces, in-order and out-of-order arrival, a duplicate, a missing position, inconsistent totals, and an invalid integrity check. Each case has a documented peer result and daemon-liveness result.

The filler payload must match the target branch. Main-line unknown noncritical entries consume slots in the public harness. The maintenance decoder's unknown branch does not increment at the same point, so its exact full-table test needs recognized entries that survive that source's occurrence and semantic rules. A green test that never reaches roof 30 provides no evidence for this CVE.

The impairment controls are upstream test mechanisms, not recommended production commands. They intentionally generate protocol shapes ordinary clients do not send. Reproduction belongs on authorized test peers and targets with rate limits, console access, and a recovery path. The vulnerability is a reliable daemon termination on affected code, so casual internet probing would create the harm being measured.

5.2 Verification must identify the binary that is actually running

ipsec version is a useful first observation, but it does not by itself prove that a newly installed daemon is in memory. A package can be upgraded while an old pluto PID continues to run. A container tag can be repointed while old Pods retain the previous image digest. Record package revision, source revision or vendor advisory, executable hash, process start time, and the target of /proc/<pid>/exe.

Vendor backports may keep a product version below 5.3.1. Those builds should be judged by an authoritative fixed-package statement and by source or patch evidence that the reassembly condition admits equality. Conversely, a privately labeled 5.3.1 build is not trustworthy if its source omitted the commit. Version banners are inventory fields, not standalone proof.

The patched binary requires a controlled restart. On an HA pair, update the node that is not taking new connections, start it, validate the running image, move a small share of peers, and observe full authentication and rekey before shifting more traffic. Then repeat on the former active node. A single old standby can restore exposure the moment a failover occurs.

A single gateway needs an out-of-band management path, an agreed outage, and a rollback package. Operators should identify which tunnels will renegotiate and how long their authentication dependencies take. After restart, validate a management-independent test peer first, then critical connection profiles. Rolling back to an affected package reopens the security issue and must automatically restore a validated temporary control or network restriction.

Functional regression should include realistic certificate chains, NAT-T, MOBIKE, IPv4 and IPv6, different negotiated algorithms, initiator and responder roles, long traffic selectors, and paths with known MTU constraints. Retain a baseline of fragmentation count, success rate, and latency for representative production peers, then compare it after the upgrade. This is especially important if fragmentation=no was used temporarily: a stale setting can make the patched daemon appear broken when the actual cause is a workaround left behind.

Backports across older affected releases require context review. Function signatures, logger APIs, fragment ownership, and message-digest reference handling evolved. A cherry-pick may not apply cleanly, or it may apply at a similarly named assertion in the wrong stage. The invariant to verify is stable: reassembly gets an existing SKF, equality is admitted, and every future descriptor append remains strictly guarded.

6 A 2021 refactor opened a four-year vulnerable window

The advisory names 4.6 as the affected starting release. Comparing 4.5 with later history maps that boundary to commit 1a4715ea4b8501da60cdd72828d8d8d6e10f8aeb, authored on September 18, 2021. The change factors fragment decryption and reconstruction out of ikev2_decrypt_msg() and lets the upper state-packet path coordinate them explicitly.

The refactor has a sensible engineering purpose. The older function selected SK or SKF, verified fragments, reconstructed plaintext, decrypted ordinary SK, and returned one boolean that represented several failure modes. The newer arrangement introduces decrypt_v2_incoming_fragments(), separates collection from reconstruction, and clarifies when all fragments have been authenticated. It also supports cleaner coordination with background cryptographic work.

The vulnerability entered while moving a boundary condition. In 4.5, ikev2_reassemble_fragments() checks whether digest_roof >= elemsof(digest). When true, it logs “packet contains too many payloads; discarded” and returns false. A full table is treated as peer-controlled resource exhaustion. The daemon remains available.

Commit 1a4715ea4b removes that recoverable block and inserts passert(md->digest_roof < elemsof(md->digest)) in the new reassembly function. The set of rejected numeric values appears similar, but the failure domain changes completely. Equality moves from a logged return to a process-wide abort. No new descriptor append accompanies the change.

The reconstructed SK is still scribbled over SKF. PAYLIMIT remains thirty, and the roof remains a one-past-the-end count. This is the decisive causal chain: a refactor altered the consequence of a network-derived state while leaving the data operation unchanged. The commit entered 4.6, matching the vendor's affected-version floor, and the assertion persisted through 5.3.

The history also prevents a wrong attribution to later unknown-payload work. Main-line commit 6824359f in 2025 makes unknown descriptors easier to retain and gives the public regression a convenient twenty-nine-entry filler. The maintenance parser differs and is still affected. The reachable fatal invariant existed years earlier and can be reached through whatever payloads that branch actually records.

6.1 The refactor changed the error domain, not the data model

Code review of refactors often concentrates on the successful path: are all fragments decrypted, is plaintext copied in order, are chains updated, and are owners released? Those behaviors remain recognizable in the 2021 diff. The security regression is in the unsuccessful path. The same capacity state that previously returned to protocol processing now enters a never-returning invariant.

Network parsers benefit from a clear classification. States caused directly by lengths, counts, enum values, ordering, or optional protocol combinations belong to recoverable input handling unless memory safety has already been lost. Invariants reserved for purely internal contradictions can remain fatal. Standards language saying a peer “MUST NOT” send something does not make the corresponding branch unreachable to an adversary.

A second review trap is semantic drift between count and index. The old >= guard made sense because inner payload decoding would need another slot. When copied into a reassembly assertion, it became an implicit claim that reconstruction itself requires spare capacity. The code below shows it does not. A familiar fixed-array expression was moved without moving the operation that justified it.

Follow-up commits such as 6a9aac807a89659c874beaaa5af2a0ba801a7c90 adjust how the message is rebuilt from fragment one. Commits 3f0b015374f5ce85cc2e99325ef80b69843e0ac4 and 30dcb882e8fe2a2fadbe5f448c93f879f6100aab continue to account for unencrypted outer payloads. They make first-fragment preservation more explicit. The fatal strict condition remains traceable to the original factoring commit.

Source archaeology changes the recommended control. If unknown payload retention were the root cause, an edge filter for unknown types might look sufficient. The 4.6 starting point and maintenance branch show that conclusion is too narrow. If fragmentation were inherently unsafe, 4.5 could not have handled the full state recoverably. The historical diff isolates the actual defect to one invariant transition.

6.2 Regression tests turn the historical boundary into a contract

Merge commit 52c1c2e039f5f7bb27d34069d2d2426fea93ec36 brings together the fix and a KVM scenario under testing/pluto/ikev2-44-cve-2026-12413-max-payloads. The initiator enables impairment hooks with ipsec whack, pads an IKE_AUTH outer chain, and requests fragmentation. The responder processes the same stages that production code uses.

Supporting commits 46339ea9fd5dd4e27c322bf327fe564fdc5d3a4d and 805814a35f22f5370deddf878ea8784f6621d083 refine the impairment and generated payload sequence. These hooks are valuable because they make an unusual boundary deterministic and reviewable. They are not evidence that ordinary clients emit the sequence spontaneously.

The expected console ends with peer INVALID_SYNTAX and no established tunnel. That result proves more than process survival. If the packet had been discarded before reassembly, the old assertion would remain untested. Reaching the subsequent global-budget error demonstrates that the fixed binary crossed the exact former failure point and retained its resource ceiling.

A maintenance-branch regression needs a branch-appropriate filler. Unknown entries may be parsed without advancing the roof there. Test authors should choose recognized outer payloads, document how each consumes a descriptor, and confirm with a breakpoint or debug trace that SKF brings the count to thirty. The expected endpoint remains a recoverable rejection and a stable daemon.

Property tests can make the lesson broader than one packet. Model a small capacity and enumerate empty, capacity minus one, capacity, and capacity plus one states for parser append, descriptor reuse, and inner decode. Assert that append rejects equality, reuse accepts equality, and no externally derived case terminates the process. Such a model survives payload-type changes.

Review policy can use the same contract: any diff converting a parser error into passert requires an external-reachability analysis; any capacity change needs equality and over-capacity cases; any conversion from object reuse to object append must update the precondition and full-table behavior. These are concrete checks with observable results, not generic calls for caution.

Source timeline for CVE-2026-12413: version 4.5 returns recoverably on a full table; the 2021 1a4715 refactor introduces a strict fatal assertion in 4.6; main line retains unknown descriptors in 2025; fixes 5c4369 and 6088d2 plus regression arrive in 2026 and version 5.3.1 is released.
Swipe horizontally on a narrow screen to inspect releases, commits, and changing failure semantics
Figure 5. The vulnerable window opens when a recoverable network boundary is moved into a fatal invariant. Later unknown-payload accounting changes the convenient trigger shape, while the root cause remains the 2021 error-domain migration.

7 An operational path from inventory to safe recovery

Begin with a runtime inventory. Each row should identify the node and cluster, accountable owner, public or partner-network reachability, UDP 500/4500 exposure, operating system or appliance firmware, package and source revision, current pluto PID and start time, HA role, source of connection configuration, effective IKE version, effective fragmentation setting, critical peer population, and maintenance window. A row missing version, reachability, or configuration evidence is not complete.

The upstream version rule is exact. Releases 4.6 through 5.3 enter the remediation queue; 5.3.1 is the first upstream fixed release. A distribution may backport the change to an older-looking package, in which case its security advisory and changelog define the fixed package. A label such as “Libreswan 5” or an appliance major firmware number is too coarse to support a closure decision.

Configuration must be evaluated after inheritance and generation. ipsec readwriteconf --config /etc/ipsec.conf can resolve also= relationships and validate syntax, while ipsec connectionstatus exposes running connections. Managed products may generate files from a controller or database. Searching one source file for fragmentation=no can miss includes, defaults, and runtime-loaded profiles.

The absence of a fragmentation directive usually means the default yes. One IKEv2 connection allowing fragmentation keeps the parser route reachable in the shared process. A safe profile on the same daemon does not contain the process-wide assertion reached through another profile. Multi-tenant inventories need effective values per connection and a mapping back to the daemon that owns them.

Prioritize by the product of reachability, vulnerable build, business concentration, and recovery difficulty. Public single-node remote access, shared multi-tenant control planes, and sites without out-of-band access lead the queue. Private peer-only gateways with tested HA may follow, but every affected build receives a date and owner. A low current traffic volume is not an exemption from a pre-authentication parser flaw.

7.1 The temporary workaround needs interoperability evidence

Libreswan's configuration workaround is fragmentation=no on every IKEv2 connection when fragmentation is not needed. It prevents negotiation and use of RFC 7383, removing the vulnerable reassembly route. If a connection requires fragmentation, the advisory provides no equivalent configuration workaround. That gateway needs a fixed package or a correctly backported patch.

“Not needed” must be measured. IKE_AUTH size grows with certificate chains, certificate requests, OCSP material, EAP exchanges, multiple traffic selectors, and vendor extensions. NAT-T adds encapsulation overhead, and mobile or tunneled paths can have lower MTUs. A short PSK test may succeed after the change while a certificate peer fails during renewal or from a different network.

Before disabling the feature, collect recent SKF use, IKE_AUTH sizes, retransmission patterns, and path-MTU observations from representative peers. Include the longest certificate chain, IPv6, mobile access, cloud firewalls, NAT devices, and cross-region paths. If SKF has appeared during the observation window, the feature already has an operational consumer. Even without prior sightings, force a complete new authentication and rekey.

A change record should name every connection, the exact configuration diff, approving owners, start and expiry, health probes, failure thresholds, and rollback. One concrete acceptance rule is to export the resolved configuration before editing, disable fragmentation connection by connection, reload or restart under control, require named peers to establish fresh IKE and Child SAs within five minutes, and hold the error rate below two standard deviations above baseline for fifteen consecutive minutes. Any failure restores the configuration and moves the node behind a narrower network boundary until patching.

Stable site-to-site deployments can supplement the workaround with an allowlist for known peer addresses. Remote-access deployments usually cannot. NAT gateways may aggregate many users, peer addresses can change, and an allowed system can itself be compromised. An ACL reduces current reachability but does not make an affected binary safe. Give every temporary rule an owner and expiry tied to the patch date.

Rate limits should count half-open IKE SAs and failed exchanges per source or risk group, not just raw UDP packets. Legitimate fragmented authentication naturally contains multiple packets. A crude packet-per-second threshold can block the exact certificate-heavy users who need RFC 7383. Useful controls distinguish repeated initialization, unusually dense outer chains, and fast failure/restart cycles while considering shared NAT.

If emergency isolation is required, preserve one complete sample before filtering. Capture the relevant flow metadata and host logs, then constrain the ingress at the earliest shared point. A rule applied only to the crashing node may send the next exchange to another vulnerable HA member. Verify enforcement across load balancers, anycast routes, security groups, and node-local firewalls.

7.2 Upgrade, restart, and regression belong in one closure record

During installation, retain package-manager output, repository origin, signature verification, source package revision, and change ticket. Source builds need a pinned commit, compiler configuration, dependency record, and artifact hash. The advisory's patch section has carried a transposed link text using CVE-2026-21413; the correct directory is CVE-2026-12413. Verify the downloaded content and commit instead of trusting a visually similar URL.

For a rolling deployment, patch the node that is not accepting new sessions. Start it with the new binary, confirm PID, executable target, build identity, and clean startup logs, then route a small test population. Exercise IKE_SA_INIT, IKE_AUTH, Child SA creation, DPD, rekey, deletion, and re-establishment before raising weight. Repeat until every active and standby member uses the fixed artifact.

For a single node, prepare out-of-band management and a signed rollback package. Record critical peers and expected renegotiation times. Validate a test connection that does not depend on the management tunnel before restoring production. If a business regression forces rollback to affected code, immediately reapply the tested workaround or ingress restriction and reopen the vulnerability record.

The functional matrix covers actual peer diversity. The security matrix covers roof 29, roof 30, an attempted thirty-first descriptor, invalid fragment zero, total above 32, first-fragment Next Payload NONE, later-fragment nonzero Next Payload, duplicate, missing and reordered fragments, and a bad integrity check. Each row states expected peer response, expected log, PID stability, and control-tunnel health.

The full-table acceptance criteria have four parts: instrumentation or logs show that reassembly was reached; the former assertion does not appear; the crafted exchange ends in INVALID_SYNTAX or a branch-equivalent recoverable error; and PID plus service invocation remain unchanged. Missing any one part means the test may have stopped too early or hidden a crash behind automatic restart.

Keep enhanced monitoring for at least one normal business rekey interval. Watch authentication failures, fragment receipt and decryption, too-many-payload diagnostics, service restarts, cores, Child SA churn, backend latency, and user reports. If long-certificate peers begin retransmitting, check for a residual fragmentation=no before attributing the issue to the security patch.

Security and service owners jointly sign completion. The record contains the running-build proof, effective configuration export, disposition of temporary controls, functional and boundary results, historical log review, active monitoring, and rollback evidence. This transforms “the package was updated” into a state that another engineer can independently verify.

Operational closure for Libreswan CVE-2026-12413: inventory runtime and effective configuration, install 5.3.1 or a vendor backport, restart in a controlled sequence, validate legitimate fragmentation, exercise the roof-30 regression, monitor PID and logs, and sign evidence.
Swipe horizontally on a narrow screen to inspect inputs, acceptance conditions, and rollback at each stage
Figure 6. Closure is a combination of running binary, effective connection settings, interoperability, and the exact full-table safety result. A package version alone cannot represent all four.

8 Observation, incident evidence, and final judgment

The strongest detection joins an unusual fragment set on the network with a process termination on the gateway. Fragmented IKE_AUTH is legitimate in many environments, and a pluto restart can come from maintenance or another defect. A dense, integrity-valid first-fragment outer chain immediately followed by the matching assertion stack creates a much stronger attribution than either observation alone.

Host searches should include reassemble_v2_incoming_fragments, digest_roof, passert, SIGABRT, status 6, core dumped, and unexpected restart. Source line numbers differ across affected branches and vendor backports, so function and expression are more stable keys. Preserve journal monotonic time, boot and invocation identifiers, PID, build ID, and coredump metadata.

When a core exists, analyze it on a restricted system. Obtain the stack, roof and capacity, SKF-chain pointer, fragment-one header, set total and count, message ID, and IKE SPIs. Memory can include private identities, certificate material, addresses, and keying state. Access, retention, and redaction should follow the organization's rules for VPN secrets, and external reports should contain a minimized technical summary.

Network evidence includes the five-tuple, NAT-T marker, initiator and responder SPI, exchange type, message ID, fragment number and total, outer Next Payload chain, critical bits, and packet sizes. Most of this structure is visible without decryption. Exact slot consumption still depends on the deployed decoder branch, so packet shape is joined with source identity and host state before making a final claim.

If no packet capture exists, reconstruct the window from IKE debug logs, flow records, conntrack, edge-firewall telemetry, and the restart timeline. Every post-crash attempt needs a new IKE SA, which can produce repeated IKE_SA_INIT exchanges, short-lived SPI pairs, and similar fragment totals. Evidence that only shows SIGABRT should be labeled consistent with the CVE until the stack or message state confirms it.

8.1 Incident response must preserve evidence and protect recovery

During active repetition, preserve one complete host-and-network sample, then constrain the ingress before repeatedly restarting the service. Stable partner environments can move to a temporary allowlist. Remote access can apply per-source or risk-aware half-open-SA controls. The response objective is to give a fixed node time to assume service without exposing every restart to the same sequence.

Move new sessions to verified fixed nodes and isolate affected members. Confirm the version on standbys, autoscaled instances, and disaster-recovery capacity before failover. Shared virtual IPs and stateless front ends can deliver the next malicious exchange to any eligible node. A local block on the first crash target is not cluster containment.

Install the fixed build, perform the controlled restart, and validate a normal peer first. Then replay the authorized boundary case in a lab or isolated production canary. Compare PID, journal, peer result, and control connection. Release temporary restrictions gradually while watching the same source and outer-chain pattern.

Historical review should extend from the present event back through the organization's log retention or the earliest affected deployment. Search for repeated SIGABRT, short restart cycles, unexplained VPN flaps, and concurrent fragmented IKE traffic. The CVE does not provide persistence, but outages can cause emergency routing changes, policy relaxations, fail-open behavior, and monitoring gaps that need separate review.

An incident report separates three evidence layers: what pinned source proves about trigger and process exit; what this environment proves about version, configuration, packets, and logs; and what remains unavailable, such as a core or reliable source attribution. That structure supports a firm technical conclusion without inventing facts that the environment did not record.

If the same stack appears after patching, first verify that the core belongs to the new PID and build, that the container did not reuse an old layer, and that the vendor backport changed the corresponding function. If the assertion expression or stack differs, begin a new source analysis. Grouping every later IKE crash under one CVE would hide distinct defects.

8.2 Final conclusion: boundary semantics and error domain created the outage

The evidence forms one continuous chain. msg_digest stores at most thirty payload descriptors, and roof denotes the half-open end. RFC 7383 permits outer payloads before SKF in fragment one. An initiator can protect IKE_AUTH after IKE_SA_INIT while identity authentication is still incomplete. A branch-specific outer sequence can therefore bring a valid roof of thirty to reassembly.

The reassembly operation reuses the SKF descriptor to represent SK. It neither appends a thirty-first object nor indexes the roof. The old strict assertion rejects that representable state and sends it through a fatal logging function to abort(). Repetition lets an untrusted peer keep a shared VPN control plane unstable.

The fixed assertion admits equality. Inner decoding then applies the unchanged global budget and returns INVALID_SYNTAX. This result preserves both bounded resource use and process availability. The malicious tunnel fails, while unrelated IKE SAs remain under a live daemon.

History locates the regression. Version 4.5 handled a full table through a logged return. The 2021 factoring commit moved that condition into passert without changing descriptor reuse, and the change entered 4.6. Main-line unknown-payload retention later provided a convenient regression shape; its accounting differs from the 5.3.1 maintenance parser and does not define the root cause.

The adjacent review covered the one-based fragment array, the 32-fragment cap, bounded aggregate size, reconstruction copy, and all roof uses in the pinned tree. It did not establish a second memory-safety vulnerability. That conclusion applies to the reviewed upstream revision and published limits; a product fork that changes them needs its own calculation.

The broader engineering lesson is concrete. A count may equal capacity; an index may not. Network-derived resource states belong in recoverable protocol handling. Refactors must preserve the severity of failure paths, not just their successful output. In-place conversions should be reviewed with object address, ownership, chain membership, and count visible together.

Closure can be expressed in four observations: the crafted boundary input reaches reassembly, the original PID remains alive, the peer receives a recoverable rejection, and a clean peer can establish and complete rekey afterward. Pair those runtime facts with traceable build and configuration evidence. When all are present, CVE-2026-12413 is closed as an engineering risk.

References

  1. Libreswan CVE-2026-12413 security advisory
  2. Libreswan CVE-2026-12413 patch and material directory
  3. Libreswan security advisory index
  4. Libreswan project and 5.3.1 release entry
  5. Libreswan 5.3.1 source release
  6. CVE.org record for CVE-2026-12413
  7. NVD record, CVSS, CWE, and CISA SSVC data
  8. Maintenance fix commit 5c4369af
  9. Main-line fix commit 6088d294
  10. Fix and max-payload regression merge 52c1c2e0
  11. 2021 factoring commit that introduced the fatal assertion
  12. Fragment-one reconstruction follow-up 6a9aac80
  13. Main-line unknown-payload preservation 6824359f
  14. Reassembled unencrypted-payload handling 3f0b0153
  15. Outer-payload inclusion change 30dcb882
  16. IKEv2 unknown-payload impairment 46339ea9
  17. Test payload-padding correction 805814a3
  18. Upstream CVE KVM regression directory
  19. Regression initiator impairment script
  20. Expected console and INVALID_SYNTAX result
  21. Pinned demux.h: PAYLIMIT, digest, and digest_roof
  22. Pinned ikev2.c: outer and inner payload decoding
  23. Pinned ikev2_message.c: collection, verification, and reassembly
  24. Pinned ikev2_send.h: one-based fragment array
  25. Pinned passert.c: logging followed by abort()
  26. Libreswan v4.5 recoverable-boundary baseline
  27. Libreswan v4.6 affected-start tag
  28. Libreswan v5.3.1 fixed tag
  29. RFC 7383, Internet Key Exchange Protocol Version 2 Message Fragmentation
  30. RFC 7296, Internet Key Exchange Protocol Version 2
  31. IANA IKEv2 parameters and payload-type registry
  32. Debian Security Tracker for CVE-2026-12413
  33. CWE-193, Off-by-one Error
  34. CWE-617, Reachable Assertion
  35. Libreswan ipsec.conf fragmentation semantics
  36. Libreswan ipsec readwriteconf configuration expansion
  37. Libreswan ipsec connectionstatus runtime inventory

Research record

9Evidence, objects, and sources

The material below preserves the identifiers and references used in this report.

9.1Research objects

Products, actors, techniques, affected objects, and control points discussed in the report.

CVECVE-2026-12413

Pre-authentication reachable assertion in Libreswan IKEv2 fragment reassembly

Affected releasesLibreswan 4.6–5.3

Upstream affected range following the 2021 refactor

Fixed releaseLibreswan 5.3.1

First upstream fixed release, published June 24, 2026

Fix commits5c4369af / 6088d294

Maintenance and main-line changes admitting a roof equal to 30

Introducing commit1a4715ea4b8501da60cdd72828d8d8d6e10f8aeb

2021 factoring change that replaced a recoverable full-table return with a fatal assertion

Key functionreassemble_v2_incoming_fragments()

Fragment reassembly routine that reuses the SKF descriptor as SK

Boundary statedigest_roof == 30

Legal full-capacity count for valid array indices zero through 29

Temporary controlfragmentation=no

Suitable only when every affected IKEv2 connection is proven not to require RFC 7383

9.2Event chronology

  1. Refactor introduces the fatal boundary

    Commit 1a4715ea replaces the recoverable full-table return and enters version 4.6.

  2. Issue reported to the project

    Libreswan receives the private report of a full fragment table terminating the daemon.

  3. Fix and 5.3.1 released

    The project publishes maintenance and main-line fixes, the fixed release, and its advisory.

  4. CVE record published

    The CNA documents network reachability, affected versions, and availability impact.

  5. SOSEC completes extended source review

    The review pins 5.3.1 and connects protocol, functions, history, regression behavior, and production closure.

9.3Sources and material

  1. Libreswan CVE-2026-12413 security advisoryhttps://libreswan.org/security/CVE-2026-12413/CVE-2026-12413.txt
  2. Libreswan CVE-2026-12413 patch and material directoryhttps://libreswan.org/security/CVE-2026-12413/
  3. Libreswan security advisory indexhttps://libreswan.org/security/
  4. Libreswan project and 5.3.1 release entryhttps://libreswan.org/
  5. Libreswan 5.3.1 source releasehttps://download.libreswan.org/libreswan-5.3.1.tar.gz
  6. CVE.org record for CVE-2026-12413https://www.cve.org/CVERecord?id=CVE-2026-12413
  7. NVD record, CVSS, CWE, and CISA SSVC datahttps://nvd.nist.gov/vuln/detail/CVE-2026-12413
  8. Maintenance fix commit 5c4369afhttps://github.com/libreswan/libreswan/commit/5c4369afcfe95a924ee0a96a0b29a7204dc40504
  9. Main-line fix commit 6088d294https://github.com/libreswan/libreswan/commit/6088d294b8a6448df1611b98d778d85482646586
  10. Fix and max-payload regression merge 52c1c2e0https://github.com/libreswan/libreswan/commit/52c1c2e039f5f7bb27d34069d2d2426fea93ec36
  11. 2021 factoring commit that introduced the fatal assertionhttps://github.com/libreswan/libreswan/commit/1a4715ea4b8501da60cdd72828d8d8d6e10f8aeb
  12. Fragment-one reconstruction follow-up 6a9aac80https://github.com/libreswan/libreswan/commit/6a9aac807a89659c874beaaa5af2a0ba801a7c90
  13. Main-line unknown-payload preservation 6824359fhttps://github.com/libreswan/libreswan/commit/6824359f416a1eba0fabc0f19d5fe9267338bf3b
  14. Reassembled unencrypted-payload handling 3f0b0153https://github.com/libreswan/libreswan/commit/3f0b015374f5ce85cc2e99325ef80b69843e0ac4
  15. Outer-payload inclusion change 30dcb882https://github.com/libreswan/libreswan/commit/30dcb882e8fe2a2fadbe5f448c93f879f6100aab
  16. IKEv2 unknown-payload impairment 46339ea9https://github.com/libreswan/libreswan/commit/46339ea9fd5dd4e27c322bf327fe564fdc5d3a4d
  17. Test payload-padding correction 805814a3https://github.com/libreswan/libreswan/commit/805814a35f22f5370deddf878ea8784f6621d083
  18. Upstream CVE KVM regression directoryhttps://github.com/libreswan/libreswan/tree/v5.3.1/testing/pluto/ikev2-44-cve-2026-12413-max-payloads
  19. Regression initiator impairment scripthttps://github.com/libreswan/libreswan/blob/v5.3.1/testing/pluto/ikev2-44-cve-2026-12413-max-payloads/westrun.sh
  20. Expected console and INVALID_SYNTAX resulthttps://github.com/libreswan/libreswan/blob/v5.3.1/testing/pluto/ikev2-44-cve-2026-12413-max-payloads/west.console.txt
  21. Pinned demux.h: PAYLIMIT, digest, and digest_roofhttps://github.com/libreswan/libreswan/blob/de24519eb4ff49803a21d75e880c87834cf33beb/programs/pluto/demux.h
  22. Pinned ikev2.c: outer and inner payload decodinghttps://github.com/libreswan/libreswan/blob/de24519eb4ff49803a21d75e880c87834cf33beb/programs/pluto/ikev2.c
  23. Pinned ikev2_message.c: collection, verification, and reassemblyhttps://github.com/libreswan/libreswan/blob/de24519eb4ff49803a21d75e880c87834cf33beb/programs/pluto/ikev2_message.c
  24. Pinned ikev2_send.h: one-based fragment arrayhttps://github.com/libreswan/libreswan/blob/de24519eb4ff49803a21d75e880c87834cf33beb/programs/pluto/ikev2_send.h
  25. Pinned passert.c: logging followed by abort()https://github.com/libreswan/libreswan/blob/de24519eb4ff49803a21d75e880c87834cf33beb/lib/libswan/passert.c
  26. Libreswan v4.5 recoverable-boundary baselinehttps://github.com/libreswan/libreswan/tree/v4.5
  27. Libreswan v4.6 affected-start taghttps://github.com/libreswan/libreswan/tree/v4.6
  28. Libreswan v5.3.1 fixed taghttps://github.com/libreswan/libreswan/tree/v5.3.1
  29. RFC 7383, Internet Key Exchange Protocol Version 2 Message Fragmentationhttps://www.rfc-editor.org/rfc/rfc7383
  30. RFC 7296, Internet Key Exchange Protocol Version 2https://www.rfc-editor.org/rfc/rfc7296
  31. IANA IKEv2 parameters and payload-type registryhttps://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml
  32. Debian Security Tracker for CVE-2026-12413https://security-tracker.debian.org/tracker/CVE-2026-12413
  33. CWE-193, Off-by-one Errorhttps://cwe.mitre.org/data/definitions/193.html
  34. CWE-617, Reachable Assertionhttps://cwe.mitre.org/data/definitions/617.html
  35. Libreswan ipsec.conf fragmentation semanticshttps://libreswan.org/man/ipsec.conf.5.html
  36. Libreswan ipsec readwriteconf configuration expansionhttps://libreswan.org/man/ipsec-readwriteconf.8.html
  37. Libreswan ipsec connectionstatus runtime inventoryhttps://libreswan.org/man/ipsec-connectionstatus.8.html