Add libssh2 CVE-2026-55200 benign PoC
This commit is contained in:
@@ -15,6 +15,7 @@ Most folders contain one of my former standalone PoC repos, preserved with its o
|
|||||||
| `ghidra-12.1.2-rce-ace-calc-poc` | `52dee6362990c03c0d753d074c85428824d46368` | 9 |
|
| `ghidra-12.1.2-rce-ace-calc-poc` | `52dee6362990c03c0d753d074c85428824d46368` | 9 |
|
||||||
| `gitea-act-runner-container-options-poc` | `f06d78fb111732f3e7737f4c07e77ef94c4b64bf` | 4 |
|
| `gitea-act-runner-container-options-poc` | `f06d78fb111732f3e7737f4c07e77ef94c4b64bf` | 4 |
|
||||||
| `imagemagick-gs-delegate-hijack-poc` | `8140e8ee0ed78beaf5e8303a795b70b138f5891b` | 5 |
|
| `imagemagick-gs-delegate-hijack-poc` | `8140e8ee0ed78beaf5e8303a795b70b138f5891b` | 5 |
|
||||||
|
| `libssh2-cve-2026-55200-poc` | direct entry, June 23, 2026 | 3 |
|
||||||
| `lunar-modrinth-chain-poc` | `ffd02120708b6503f11585858ce3724872f3b7a7` | 6 |
|
| `lunar-modrinth-chain-poc` | `ffd02120708b6503f11585858ce3724872f3b7a7` | 6 |
|
||||||
| `mybb-limited-acp-to-admin` | `1610e0373943c2f6562a99f917d3a3d1fdd9056d` | 5 |
|
| `mybb-limited-acp-to-admin` | `1610e0373943c2f6562a99f917d3a3d1fdd9056d` | 5 |
|
||||||
| `nmap-ipv6-extlen-wrap-poc` | direct entry, June 23, 2026 | 4 |
|
| `nmap-ipv6-extlen-wrap-poc` | direct entry, June 23, 2026 | 4 |
|
||||||
@@ -39,4 +40,4 @@ Matching Git blob IDs means the tracked file bytes are identical. The check cove
|
|||||||
|
|
||||||
This repository preserves the contents of those PoCs. Repository-level metadata such as stars, issues, pull requests, releases, and separate Git history are not represented inside the folders.
|
This repository preserves the contents of those PoCs. Repository-level metadata such as stars, issues, pull requests, releases, and separate Git history are not represented inside the folders.
|
||||||
|
|
||||||
Direct entries, including `nmap-ipv6-extlen-wrap-poc`, are authored in this repository and are tracked by this repository's commit history.
|
Direct entries, including `libssh2-cve-2026-55200-poc` and `nmap-ipv6-extlen-wrap-poc`, are authored in this repository and are tracked by this repository's commit history.
|
||||||
|
|||||||
164
libssh2-cve-2026-55200-poc/README.md
Normal file
164
libssh2-cve-2026-55200-poc/README.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
# libssh2 CVE-2026-55200 benign PoC
|
||||||
|
|
||||||
|
Benign local proof of concept for CVE-2026-55200, an unchecked SSH `packet_length` condition in libssh2's `ssh2_transport_read()` transport parser path.
|
||||||
|
|
||||||
|
Research status: verified local arithmetic and state model.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
libssh2 through 1.11.1 accepted an attacker-controlled SSH packet length in one full-packet decryption path without first enforcing the RFC-sized libssh2 packet maximum.
|
||||||
|
|
||||||
|
The vulnerable shape is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
total_num = 4
|
||||||
|
packet_length = decoded SSH packet_length field
|
||||||
|
reject only if packet_length < 1
|
||||||
|
total_num += packet_length + mac_len + auth_len
|
||||||
|
reject if total_num > 35000 or total_num == 0
|
||||||
|
allocate total_num bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
On a build where `size_t` is 32-bit, a packet length of `0xffffffff` with `auth_len=16` wraps the computed allocation size to 19 bytes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
4 + 0xffffffff + 0 + 16 == 19 modulo 2^32
|
||||||
|
```
|
||||||
|
|
||||||
|
The original `packet_length` remains `0xffffffff`. Later full-packet processing can still use packet-length-derived sizes, including a `packet_length - 1` style length, after the allocation decision has already been made.
|
||||||
|
|
||||||
|
The upstream fix rejects `packet_length > LIBSSH2_PACKET_MAXPAYLOAD` before the addition.
|
||||||
|
|
||||||
|
This PoC does not generate SSH traffic, does not attempt remote exploitation, and does not perform an out-of-bounds write. It is a standalone verifier for the arithmetic and decision-state transition.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `poc/cve_2026_55200_probe.c` - standalone C11 benign verifier with no source comments
|
||||||
|
- `evidence/2026-06-23-local-harness-output.txt` - local build and replay evidence
|
||||||
|
|
||||||
|
## Affected Source Path
|
||||||
|
|
||||||
|
The affected path is in:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/transport.c:ssh2_transport_read()
|
||||||
|
```
|
||||||
|
|
||||||
|
The fix is upstream commit:
|
||||||
|
|
||||||
|
```text
|
||||||
|
97acf3dfda80c91c3a8c9f2372546301d4a1a7a8
|
||||||
|
```
|
||||||
|
|
||||||
|
The relevant fix adds a `packet_length > LIBSSH2_PACKET_MAXPAYLOAD` guard before the vulnerable addition in the full-packet path.
|
||||||
|
|
||||||
|
## Build and Run
|
||||||
|
|
||||||
|
Linux, macOS, WSL, or MinGW:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gcc -std=c11 -Wall -Wextra -O0 -g -o cve_2026_55200_probe poc/cve_2026_55200_probe.c
|
||||||
|
./cve_2026_55200_probe
|
||||||
|
```
|
||||||
|
|
||||||
|
Windows PowerShell with MinGW:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
gcc -std=c11 -Wall -Wextra -O0 -g -o cve_2026_55200_probe.exe .\poc\cve_2026_55200_probe.c
|
||||||
|
.\cve_2026_55200_probe.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
No arguments defaults to the benign proof mode. The same mode can be selected explicitly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cve_2026_55200_probe --benign
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional diagnostic modes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cve_2026_55200_probe --native
|
||||||
|
./cve_2026_55200_probe --check
|
||||||
|
```
|
||||||
|
|
||||||
|
The input values can be overridden:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cve_2026_55200_probe --packet-length 0xffffffff --mac-len 0 --auth-len 16
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Output
|
||||||
|
|
||||||
|
On a 64-bit build, the default benign run should report `result=PASS`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
benign CVE-2026-55200 proof
|
||||||
|
build_size_t_bytes=8
|
||||||
|
build_size_t_bits=64
|
||||||
|
packet_length=0xffffffff (4294967295)
|
||||||
|
mac_len=0
|
||||||
|
auth_len=16
|
||||||
|
mathematical_total=4294967315
|
||||||
|
vulnerable32_decision=accepted
|
||||||
|
vulnerable32_total=19
|
||||||
|
vulnerable32_allocation=19
|
||||||
|
fullpacket_style_length=4294967294
|
||||||
|
allocation_gap=4294967275
|
||||||
|
fixed32_decision=rejected: out of boundary
|
||||||
|
native_unpatched_decision=rejected: out of boundary
|
||||||
|
native_unpatched_total=4294967315
|
||||||
|
native_note=64-bit native arithmetic rejects this default input; modeled 32-bit arithmetic remains vulnerable
|
||||||
|
result=PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--native` mode shows what the current binary's actual `size_t` arithmetic does. On a 64-bit build, native arithmetic rejects the default input because the computed total does not wrap before the existing `total_num > 35000` check:
|
||||||
|
|
||||||
|
```text
|
||||||
|
native-size_t check
|
||||||
|
build_size_t_bytes=8
|
||||||
|
build_size_t_bits=64
|
||||||
|
unpatched_decision=rejected: out of boundary
|
||||||
|
unpatched_total=4294967315
|
||||||
|
unpatched_allocation=0
|
||||||
|
fixed_decision=rejected: out of boundary
|
||||||
|
fixed_total=0
|
||||||
|
fixed_allocation=0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mechanics
|
||||||
|
|
||||||
|
The verifier computes three decisions for the same input:
|
||||||
|
|
||||||
|
- `vulnerable32`: the vulnerable arithmetic using a 32-bit `size_t` stand-in
|
||||||
|
- `fixed32`: the patched 32-bit decision with the packet-length maximum check
|
||||||
|
- `native_unpatched`: the unpatched decision using the current binary's real `size_t`
|
||||||
|
|
||||||
|
For the default input, the modeled 32-bit vulnerable path accepts the packet and derives a 19-byte allocation. The fixed model rejects before allocation. A native 64-bit build also rejects the default input, but that does not invalidate the 32-bit vulnerable state; it shows the architecture dependency explicitly.
|
||||||
|
|
||||||
|
The proof condition is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
vulnerable32_decision == accepted
|
||||||
|
vulnerable32_allocation == 19
|
||||||
|
packet_length > 35000
|
||||||
|
fullpacket_style_length > vulnerable32_allocation
|
||||||
|
fixed32_decision == rejected
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why This Is Benign
|
||||||
|
|
||||||
|
The program does not connect to any host, bind any socket, generate any SSH packet, or copy past an allocation. It only models the arithmetic and branch decisions needed to distinguish the vulnerable and fixed behavior.
|
||||||
|
|
||||||
|
The `allocation_gap` value is a diagnostic showing how far a later packet-length-derived operation would exceed the modeled allocation if such an operation were performed.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- CVE record: `https://www.cve.org/CVERecord?id=CVE-2026-55200`
|
||||||
|
- NVD entry: `https://nvd.nist.gov/vuln/detail/CVE-2026-55200`
|
||||||
|
- Upstream fix: `https://github.com/libssh2/libssh2/commit/97acf3dfda80c91c3a8c9f2372546301d4a1a7a8`
|
||||||
|
- Upstream PR: `https://github.com/libssh2/libssh2/pull/2052`
|
||||||
|
- VulnCheck advisory: `https://www.vulncheck.com/advisories/libssh2-out-of-bounds-write-via-unchecked-packet-length-in-transport-c`
|
||||||
|
|
||||||
|
## Responsible Use
|
||||||
|
|
||||||
|
Run this PoC only as a local research and regression-verification harness. It is intentionally scoped to a non-network arithmetic model.
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
Repository HEAD during verification:
|
||||||
|
cef60fd581c508f1193410f2eb1a9c6cd73523ae
|
||||||
|
|
||||||
|
Compiler:
|
||||||
|
gcc.exe (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders, r7) 15.2.0
|
||||||
|
|
||||||
|
Build command:
|
||||||
|
gcc -std=c11 -Wall -Wextra -O0 -g -o libssh2-cve-2026-55200-poc\cve_2026_55200_probe.exe libssh2-cve-2026-55200-poc\poc\cve_2026_55200_probe.c
|
||||||
|
|
||||||
|
Built binary SHA256:
|
||||||
|
06BD85420C0B9DD29681637EFD83F4C5791357CEDF53859183D88018C0375920
|
||||||
|
|
||||||
|
Source comment check:
|
||||||
|
rg "//|/\*|\*/" libssh2-cve-2026-55200-poc\poc\cve_2026_55200_probe.c
|
||||||
|
exit status: 1
|
||||||
|
meaning: no comment markers matched
|
||||||
|
|
||||||
|
Default benign run:
|
||||||
|
libssh2-cve-2026-55200-poc\cve_2026_55200_probe.exe
|
||||||
|
|
||||||
|
benign CVE-2026-55200 proof
|
||||||
|
build_size_t_bytes=8
|
||||||
|
build_size_t_bits=64
|
||||||
|
packet_length=0xffffffff (4294967295)
|
||||||
|
mac_len=0
|
||||||
|
auth_len=16
|
||||||
|
mathematical_total=4294967315
|
||||||
|
vulnerable32_decision=accepted
|
||||||
|
vulnerable32_total=19
|
||||||
|
vulnerable32_allocation=19
|
||||||
|
fullpacket_style_length=4294967294
|
||||||
|
allocation_gap=4294967275
|
||||||
|
fixed32_decision=rejected: out of boundary
|
||||||
|
native_unpatched_decision=rejected: out of boundary
|
||||||
|
native_unpatched_total=4294967315
|
||||||
|
native_note=64-bit native arithmetic rejects this default input; modeled 32-bit arithmetic remains vulnerable
|
||||||
|
result=PASS
|
||||||
|
|
||||||
|
Native-size run:
|
||||||
|
libssh2-cve-2026-55200-poc\cve_2026_55200_probe.exe --native
|
||||||
|
|
||||||
|
native-size_t check
|
||||||
|
build_size_t_bytes=8
|
||||||
|
build_size_t_bits=64
|
||||||
|
unpatched_decision=rejected: out of boundary
|
||||||
|
unpatched_total=4294967315
|
||||||
|
unpatched_allocation=0
|
||||||
|
fixed_decision=rejected: out of boundary
|
||||||
|
fixed_total=0
|
||||||
|
fixed_allocation=0
|
||||||
|
|
||||||
|
Detailed arithmetic run:
|
||||||
|
libssh2-cve-2026-55200-poc\cve_2026_55200_probe.exe --check
|
||||||
|
|
||||||
|
detailed CVE-2026-55200 arithmetic check
|
||||||
|
build_size_t_bytes=8
|
||||||
|
build_size_t_bits=64
|
||||||
|
packet_length=0xffffffff (4294967295)
|
||||||
|
mac_len=0
|
||||||
|
auth_len=16
|
||||||
|
mathematical_total=4294967315
|
||||||
|
vulnerable32_total=19
|
||||||
|
vulnerable32_decision=accepted
|
||||||
|
vulnerable32_allocation=19
|
||||||
|
fullpacket_style_length=4294967294
|
||||||
|
fixed32_decision=rejected: out of boundary
|
||||||
|
native_unpatched_decision=rejected: out of boundary
|
||||||
|
native_unpatched_total=4294967315
|
||||||
|
|
||||||
|
Final scratch verification build command:
|
||||||
|
gcc -std=c11 -Wall -Wextra -O0 -g -o ..\cve_2026_55200_probe_verify.exe libssh2-cve-2026-55200-poc\poc\cve_2026_55200_probe.c
|
||||||
|
|
||||||
|
Final scratch verification binary SHA256:
|
||||||
|
7220CDCF99CE3E456D5E709CF94762FBE502B1782B4F4494FB5D4CD0C9BD5277
|
||||||
|
|
||||||
|
Final scratch verification result:
|
||||||
|
default run result=PASS
|
||||||
|
--native exited 0
|
||||||
|
--check exited 0
|
||||||
355
libssh2-cve-2026-55200-poc/poc/cve_2026_55200_probe.c
Normal file
355
libssh2-cve-2026-55200-poc/poc/cve_2026_55200_probe.c
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
#include <errno.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define LIBSSH2_PACKET_MAXPAYLOAD 35000u
|
||||||
|
|
||||||
|
enum {
|
||||||
|
POC_OK = 0,
|
||||||
|
POC_ERROR_DECRYPT = -1,
|
||||||
|
POC_ERROR_OUT_OF_BOUNDARY = -2
|
||||||
|
};
|
||||||
|
|
||||||
|
struct calc_result {
|
||||||
|
uint32_t packet_length;
|
||||||
|
uint32_t total32;
|
||||||
|
uint64_t mathematical_total;
|
||||||
|
size_t native_total;
|
||||||
|
size_t allocation_length;
|
||||||
|
int rc;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *rc_name(int rc)
|
||||||
|
{
|
||||||
|
switch(rc) {
|
||||||
|
case POC_OK:
|
||||||
|
return "accepted";
|
||||||
|
case POC_ERROR_DECRYPT:
|
||||||
|
return "rejected: packet_length < 1";
|
||||||
|
case POC_ERROR_OUT_OF_BOUNDARY:
|
||||||
|
return "rejected: out of boundary";
|
||||||
|
default:
|
||||||
|
return "rejected: unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_u32(const char *text, uint32_t *out)
|
||||||
|
{
|
||||||
|
char *end = NULL;
|
||||||
|
unsigned long long parsed;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
parsed = strtoull(text, &end, 0);
|
||||||
|
if(errno || !end || *end != '\0' || parsed > UINT32_MAX) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = (uint32_t)parsed;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clear_result(struct calc_result *r, uint32_t packet_length,
|
||||||
|
uint32_t mac_len, uint32_t auth_len)
|
||||||
|
{
|
||||||
|
memset(r, 0, sizeof(*r));
|
||||||
|
r->packet_length = packet_length;
|
||||||
|
r->mathematical_total = 4ull + packet_length + mac_len + auth_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int vulnerable32(uint32_t packet_length, uint32_t mac_len,
|
||||||
|
uint32_t auth_len, struct calc_result *r)
|
||||||
|
{
|
||||||
|
uint32_t total = 4u;
|
||||||
|
|
||||||
|
clear_result(r, packet_length, mac_len, auth_len);
|
||||||
|
|
||||||
|
if(packet_length < 1u) {
|
||||||
|
r->rc = POC_ERROR_DECRYPT;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
total += packet_length + mac_len + auth_len;
|
||||||
|
r->total32 = total;
|
||||||
|
|
||||||
|
if(total > LIBSSH2_PACKET_MAXPAYLOAD || total == 0u) {
|
||||||
|
r->rc = POC_ERROR_OUT_OF_BOUNDARY;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
r->allocation_length = (size_t)total;
|
||||||
|
r->rc = POC_OK;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fixed32(uint32_t packet_length, uint32_t mac_len,
|
||||||
|
uint32_t auth_len, struct calc_result *r)
|
||||||
|
{
|
||||||
|
uint32_t total = 4u;
|
||||||
|
|
||||||
|
clear_result(r, packet_length, mac_len, auth_len);
|
||||||
|
|
||||||
|
if(packet_length < 1u) {
|
||||||
|
r->rc = POC_ERROR_DECRYPT;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(packet_length > LIBSSH2_PACKET_MAXPAYLOAD) {
|
||||||
|
r->rc = POC_ERROR_OUT_OF_BOUNDARY;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
total += packet_length + mac_len + auth_len;
|
||||||
|
r->total32 = total;
|
||||||
|
|
||||||
|
if(total > LIBSSH2_PACKET_MAXPAYLOAD || total == 0u) {
|
||||||
|
r->rc = POC_ERROR_OUT_OF_BOUNDARY;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
r->allocation_length = (size_t)total;
|
||||||
|
r->rc = POC_OK;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int native_unpatched(uint32_t packet_length, uint32_t mac_len,
|
||||||
|
uint32_t auth_len, struct calc_result *r)
|
||||||
|
{
|
||||||
|
size_t total = 4u;
|
||||||
|
|
||||||
|
clear_result(r, packet_length, mac_len, auth_len);
|
||||||
|
|
||||||
|
if(packet_length < 1u) {
|
||||||
|
r->rc = POC_ERROR_DECRYPT;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
total += (size_t)packet_length + (size_t)mac_len + (size_t)auth_len;
|
||||||
|
r->native_total = total;
|
||||||
|
|
||||||
|
if(total > LIBSSH2_PACKET_MAXPAYLOAD || total == 0u) {
|
||||||
|
r->rc = POC_ERROR_OUT_OF_BOUNDARY;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
r->allocation_length = total;
|
||||||
|
r->rc = POC_OK;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int native_fixed(uint32_t packet_length, uint32_t mac_len,
|
||||||
|
uint32_t auth_len, struct calc_result *r)
|
||||||
|
{
|
||||||
|
size_t total = 4u;
|
||||||
|
|
||||||
|
clear_result(r, packet_length, mac_len, auth_len);
|
||||||
|
|
||||||
|
if(packet_length < 1u) {
|
||||||
|
r->rc = POC_ERROR_DECRYPT;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(packet_length > LIBSSH2_PACKET_MAXPAYLOAD) {
|
||||||
|
r->rc = POC_ERROR_OUT_OF_BOUNDARY;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
total += (size_t)packet_length + (size_t)mac_len + (size_t)auth_len;
|
||||||
|
r->native_total = total;
|
||||||
|
|
||||||
|
if(total > LIBSSH2_PACKET_MAXPAYLOAD || total == 0u) {
|
||||||
|
r->rc = POC_ERROR_OUT_OF_BOUNDARY;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
r->allocation_length = total;
|
||||||
|
r->rc = POC_OK;
|
||||||
|
return r->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t fullpacket_style_length(uint32_t packet_length)
|
||||||
|
{
|
||||||
|
if(packet_length == 0u) {
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
return (uint64_t)(packet_length - 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int run_benign(uint32_t packet_length, uint32_t mac_len,
|
||||||
|
uint32_t auth_len)
|
||||||
|
{
|
||||||
|
struct calc_result vulnerable;
|
||||||
|
struct calc_result fixed;
|
||||||
|
struct calc_result native;
|
||||||
|
uint64_t copy_len;
|
||||||
|
uint64_t gap = 0u;
|
||||||
|
int pass;
|
||||||
|
|
||||||
|
vulnerable32(packet_length, mac_len, auth_len, &vulnerable);
|
||||||
|
fixed32(packet_length, mac_len, auth_len, &fixed);
|
||||||
|
native_unpatched(packet_length, mac_len, auth_len, &native);
|
||||||
|
|
||||||
|
copy_len = fullpacket_style_length(packet_length);
|
||||||
|
if(vulnerable.rc == POC_OK && copy_len > vulnerable.allocation_length) {
|
||||||
|
gap = copy_len - (uint64_t)vulnerable.allocation_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
pass = vulnerable.rc == POC_OK &&
|
||||||
|
vulnerable.allocation_length == (size_t)(uint32_t)vulnerable.mathematical_total &&
|
||||||
|
vulnerable.packet_length > LIBSSH2_PACKET_MAXPAYLOAD &&
|
||||||
|
copy_len > vulnerable.allocation_length &&
|
||||||
|
fixed.rc == POC_ERROR_OUT_OF_BOUNDARY;
|
||||||
|
|
||||||
|
printf("benign CVE-2026-55200 proof\n");
|
||||||
|
printf("build_size_t_bytes=%zu\n", sizeof(size_t));
|
||||||
|
printf("build_size_t_bits=%zu\n", sizeof(size_t) * (size_t)CHAR_BIT);
|
||||||
|
printf("packet_length=0x%08" PRIx32 " (%" PRIu32 ")\n",
|
||||||
|
packet_length, packet_length);
|
||||||
|
printf("mac_len=%" PRIu32 "\n", mac_len);
|
||||||
|
printf("auth_len=%" PRIu32 "\n", auth_len);
|
||||||
|
printf("mathematical_total=%" PRIu64 "\n",
|
||||||
|
vulnerable.mathematical_total);
|
||||||
|
printf("vulnerable32_decision=%s\n", rc_name(vulnerable.rc));
|
||||||
|
printf("vulnerable32_total=%" PRIu32 "\n", vulnerable.total32);
|
||||||
|
printf("vulnerable32_allocation=%zu\n", vulnerable.allocation_length);
|
||||||
|
printf("fullpacket_style_length=%" PRIu64 "\n", copy_len);
|
||||||
|
printf("allocation_gap=%" PRIu64 "\n", gap);
|
||||||
|
printf("fixed32_decision=%s\n", rc_name(fixed.rc));
|
||||||
|
printf("native_unpatched_decision=%s\n", rc_name(native.rc));
|
||||||
|
printf("native_unpatched_total=%zu\n", native.native_total);
|
||||||
|
if(sizeof(size_t) >= 8u && native.rc == POC_ERROR_OUT_OF_BOUNDARY) {
|
||||||
|
printf("native_note=64-bit native arithmetic rejects this default input; modeled 32-bit arithmetic remains vulnerable\n");
|
||||||
|
}
|
||||||
|
else if(sizeof(size_t) < 8u && native.rc == POC_OK) {
|
||||||
|
printf("native_note=32-bit native arithmetic reaches the wrapped allocation state\n");
|
||||||
|
}
|
||||||
|
printf("result=%s\n", pass ? "PASS" : "FAIL");
|
||||||
|
|
||||||
|
return pass ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int run_native(uint32_t packet_length, uint32_t mac_len,
|
||||||
|
uint32_t auth_len)
|
||||||
|
{
|
||||||
|
struct calc_result unpatched;
|
||||||
|
struct calc_result fixed;
|
||||||
|
|
||||||
|
native_unpatched(packet_length, mac_len, auth_len, &unpatched);
|
||||||
|
native_fixed(packet_length, mac_len, auth_len, &fixed);
|
||||||
|
|
||||||
|
printf("native-size_t check\n");
|
||||||
|
printf("build_size_t_bytes=%zu\n", sizeof(size_t));
|
||||||
|
printf("build_size_t_bits=%zu\n", sizeof(size_t) * (size_t)CHAR_BIT);
|
||||||
|
printf("unpatched_decision=%s\n", rc_name(unpatched.rc));
|
||||||
|
printf("unpatched_total=%zu\n", unpatched.native_total);
|
||||||
|
printf("unpatched_allocation=%zu\n", unpatched.allocation_length);
|
||||||
|
printf("fixed_decision=%s\n", rc_name(fixed.rc));
|
||||||
|
printf("fixed_total=%zu\n", fixed.native_total);
|
||||||
|
printf("fixed_allocation=%zu\n", fixed.allocation_length);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int run_check(uint32_t packet_length, uint32_t mac_len,
|
||||||
|
uint32_t auth_len)
|
||||||
|
{
|
||||||
|
struct calc_result vulnerable;
|
||||||
|
struct calc_result fixed;
|
||||||
|
struct calc_result native;
|
||||||
|
|
||||||
|
vulnerable32(packet_length, mac_len, auth_len, &vulnerable);
|
||||||
|
fixed32(packet_length, mac_len, auth_len, &fixed);
|
||||||
|
native_unpatched(packet_length, mac_len, auth_len, &native);
|
||||||
|
|
||||||
|
printf("detailed CVE-2026-55200 arithmetic check\n");
|
||||||
|
printf("build_size_t_bytes=%zu\n", sizeof(size_t));
|
||||||
|
printf("build_size_t_bits=%zu\n", sizeof(size_t) * (size_t)CHAR_BIT);
|
||||||
|
printf("packet_length=0x%08" PRIx32 " (%" PRIu32 ")\n",
|
||||||
|
packet_length, packet_length);
|
||||||
|
printf("mac_len=%" PRIu32 "\n", mac_len);
|
||||||
|
printf("auth_len=%" PRIu32 "\n", auth_len);
|
||||||
|
printf("mathematical_total=%" PRIu64 "\n",
|
||||||
|
vulnerable.mathematical_total);
|
||||||
|
printf("vulnerable32_total=%" PRIu32 "\n", vulnerable.total32);
|
||||||
|
printf("vulnerable32_decision=%s\n", rc_name(vulnerable.rc));
|
||||||
|
printf("vulnerable32_allocation=%zu\n", vulnerable.allocation_length);
|
||||||
|
printf("fullpacket_style_length=%" PRIu64 "\n",
|
||||||
|
fullpacket_style_length(packet_length));
|
||||||
|
printf("fixed32_decision=%s\n", rc_name(fixed.rc));
|
||||||
|
printf("native_unpatched_decision=%s\n", rc_name(native.rc));
|
||||||
|
printf("native_unpatched_total=%zu\n", native.native_total);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usage(const char *argv0)
|
||||||
|
{
|
||||||
|
printf("usage: %s [--benign|--check|--native] [options]\n", argv0);
|
||||||
|
printf("default mode: --benign\n");
|
||||||
|
printf("options:\n");
|
||||||
|
printf(" --packet-length N default 0xffffffff\n");
|
||||||
|
printf(" --mac-len N default 0\n");
|
||||||
|
printf(" --auth-len N default 16\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
enum {
|
||||||
|
MODE_BENIGN,
|
||||||
|
MODE_CHECK,
|
||||||
|
MODE_NATIVE
|
||||||
|
} mode = MODE_BENIGN;
|
||||||
|
uint32_t packet_length = UINT32_MAX;
|
||||||
|
uint32_t mac_len = 0u;
|
||||||
|
uint32_t auth_len = 16u;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 1; i < argc; i++) {
|
||||||
|
if(strcmp(argv[i], "--benign") == 0) {
|
||||||
|
mode = MODE_BENIGN;
|
||||||
|
}
|
||||||
|
else if(strcmp(argv[i], "--check") == 0) {
|
||||||
|
mode = MODE_CHECK;
|
||||||
|
}
|
||||||
|
else if(strcmp(argv[i], "--native") == 0) {
|
||||||
|
mode = MODE_NATIVE;
|
||||||
|
}
|
||||||
|
else if(strcmp(argv[i], "--packet-length") == 0 && i + 1 < argc) {
|
||||||
|
if(parse_u32(argv[++i], &packet_length)) {
|
||||||
|
fprintf(stderr, "invalid --packet-length value\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(strcmp(argv[i], "--mac-len") == 0 && i + 1 < argc) {
|
||||||
|
if(parse_u32(argv[++i], &mac_len)) {
|
||||||
|
fprintf(stderr, "invalid --mac-len value\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(strcmp(argv[i], "--auth-len") == 0 && i + 1 < argc) {
|
||||||
|
if(parse_u32(argv[++i], &auth_len)) {
|
||||||
|
fprintf(stderr, "invalid --auth-len value\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
|
||||||
|
usage(argv[0]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
usage(argv[0]);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode == MODE_BENIGN) {
|
||||||
|
return run_benign(packet_length, mac_len, auth_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode == MODE_NATIVE) {
|
||||||
|
return run_native(packet_length, mac_len, auth_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return run_check(packet_length, mac_len, auth_len);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user