359 lines
11 KiB
C
359 lines
11 KiB
C
#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 += packet_length + mac_len + 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 += packet_length + mac_len + 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(native.rc == POC_OK && sizeof(size_t) >= 8u) {
|
|
printf("native_note=source-shaped integer expression wraps before assignment into 64-bit size_t\n");
|
|
}
|
|
else if(sizeof(size_t) >= 8u && native.rc == POC_ERROR_OUT_OF_BOUNDARY) {
|
|
printf("native_note=64-bit native arithmetic rejects when each operand is widened before addition\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);
|
|
}
|