Add restricted wolfSSH ordering fix

Apply hash-pinned generated edits for CVE-2025-14942 while keeping
wolfSSH 1.4.20 managed sources unchanged. Add the ABI header overlay,
provenance records, and real state-machine interoperability contracts.
This commit is contained in:
2026-09-16 14:04:34 +02:00
parent 4d3bb490c9
commit bea33e1c95
28 changed files with 4653 additions and 64 deletions
+33 -3
View File
@@ -117,6 +117,28 @@ def enum_containing(source, token):
return matches[0] + "\n"
def reviewed_order_function(name, original):
"""Independent, exact allowlist; do not accept an arbitrary generator delta."""
if name == 'SendExtInfo':
return ('int SendExtInfo(WOLFSSH* ssh)\n{\n'
' WOLFSSH_UNUSED(ssh);\n return WS_NOT_COMPILED;\n}\n')
if name == 'SendKexInit':
edits = (
(' ssh->isKeying = 1;',
' /* Set self is keying flag since we started sending the KEX init msg */\n'
' ssh->isKeying |= WOLFSSH_SELF_IS_KEYING;'),
(' if (ssh->ctx->side == WOLFSSH_ENDPOINT_CLIENT) {\n'
' kexAlgoNamesPlus = ",ext-info-c";\n'
' kexAlgoNamesPlusSz = (word32)WSTRLEN(kexAlgoNamesPlus);\n }\n\n', ''),
(' if (ret == WS_SUCCESS)\n ret = wolfSSH_SendPacket(ssh);',
' if (ret == WS_SUCCESS) {\n ret = wolfSSH_SendPacket(ssh);\n }'),
)
for old, new in edits:
assert original.count(old) == 1, name
original = original.replace(old, new)
return original
def main():
parser = argparse.ArgumentParser(description=__doc__)
databases = sorted((ROOT / ".pio/build").glob("*/compile_commands.json"))
@@ -182,15 +204,23 @@ def main():
functions = ("NameToId", "IdToName", "AlgoListSz", "CopyNameList",
"CopyNameListPlus", "BuildNameList", "SendKexInit", "SendExtInfo")
actual = "\n".join(extract(sources["ssh.c"], "wolfSSH_CTX_SetAlgoList" + field)
for field in FIELDS)
ssh_entry = next(e for e in ENTRIES if e.name == 'wolfssh_ssh')
_, generated_ssh = render_entry(ssh_entry, {'project': ROOT})
actual = ''
for field in FIELDS:
name = 'wolfSSH_CTX_SetAlgoList' + field
body = extract(generated_ssh.decode(), name)
assert body == extract(sources['ssh.c'], name), name
actual += body
for name in functions:
if extract(internal, name) != extract(sources["internal.c"], name):
if extract(internal, name) != reviewed_order_function(name, extract(sources["internal.c"], name)):
raise RuntimeError(f"Override changed reviewed protocol function: {name}")
actual += "\n".join(extract(internal, name) for name in functions)
# Preserve actual conditional enum values and feature-filtered name table.
types = "\n".join(enum_containing(resolved, token) for token in
("ID_NONE", "TYPE_KEX", "MSGID_KEXINIT", "WOLFSSH_ENDPOINT_SERVER"))
assert macros['WOLFSSH_SELF_IS_KEYING'] == '0x02'
types += '#define WOLFSSH_SELF_IS_KEYING 0x02\n'
types += "typedef struct { byte id; byte type; const char *name; } NameIdPair;\n" + mapping
assignments = []
for field in FIELDS:
+9 -11
View File
@@ -7,6 +7,7 @@
#include "resolved.h"
#define WLOG(...) ((void)0)
#define WOLFSSH_UNUSED(x) ((void)(x))
#define INLINE inline
#define WMEMCPY memcpy
#define WSTRLEN strlen
@@ -18,9 +19,7 @@
#define LENGTH_SZ 4U
#define BOOLEAN_SZ 1U
#define COOKIE_SZ 16U
#define WS_EXTINFO_EXTENSION_COUNT 1
static const char cannedNoneNames[] = "none";
static const char serverSigAlgsName[] = "server-sig-algs";
typedef struct { byte *kexInit; word32 kexInitSz; } HandshakeInfo;
typedef struct {
@@ -206,14 +205,13 @@ int main(void)
assert(SendKexInit(ssh) == WS_SUCCESS);
check_kex(ssh);
assert(sends == 2 && allocations == 2 && frees == 1);
assert(SendExtInfo(ssh) == WS_SUCCESS);
size_t offset = 1, length = ssh->outputBuffer.length - 8U;
const byte *p = packet + 8;
assert(p[0] == MSGID_EXT_INFO);
assert(take_u32(p, length, &offset) == 1);
expect_name(p, length, &offset, "server-sig-algs");
expect_name(p, length, &offset, "ssh-ed25519,ecdsa-sha2-nistp256");
assert(offset == length);
assert(ssh->isKeying == WOLFSSH_SELF_IS_KEYING);
assert(SendExtInfo(ssh) == WS_NOT_COMPILED);
assert(sends == 2);
ctx.side = WOLFSSH_ENDPOINT_CLIENT;
assert(SendKexInit(ssh) == WS_SUCCESS);
check_kex(ssh); /* Client must not append ext-info-c either. */
ctx.side = WOLFSSH_ENDPOINT_SERVER;
/* No key and injected packet/allocation failures must not send a fallback. */
unsigned before = sends;
ctx.privateKeyCount = 0;
@@ -234,6 +232,6 @@ int main(void)
assert(purges == old_purges);
check_kex(ssh);
bounded_free(handshake.kexInit);
puts("PASS: resolved vendor name/ID/type map, actual setters, initial/rekey KEXINIT both directions, server-sig-algs, bounded failure paths");
puts("PASS: resolved vendor name/ID/type map, actual setters, initial/rekey KEXINIT both directions, no EXT_INFO negotiation, bounded failure paths");
return 0;
}