Add libssh2 CVE-2026-55200 benign PoC
This commit is contained in:
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