From b988582cc18c4b0b7615e3fba376acf98e337661 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 17 Apr 2026 23:46:19 +0200 Subject: [PATCH 01/36] Add DER version of server5-selfsigned.crt Signed-off-by: Gilles Peskine --- data_files/Makefile | 4 ++++ data_files/server5-selfsigned.crt.der | Bin 0 -> 459 bytes 2 files changed, 4 insertions(+) create mode 100644 data_files/server5-selfsigned.crt.der diff --git a/data_files/Makefile b/data_files/Makefile index 71277b9d8..b8f1c39f2 100644 --- a/data_files/Makefile +++ b/data_files/Makefile @@ -469,6 +469,10 @@ server5-selfsigned.crt: server5.key -out $@ all_final += server5-selfsigned.crt +server5-selfsigned.crt.der: server5-selfsigned.crt + $(OPENSSL) x509 -inform PEM -in $< -outform DER -out $@ +all_final += server5-selfsigned.crt.der + # Create a certificate which is almost identical to "server3.crt", i.e. # it contains a public EC key and it is signed with RSA. The main difference # compared to "server3.crt" is that in this case we use a secp256r1 key ("server5.key") diff --git a/data_files/server5-selfsigned.crt.der b/data_files/server5-selfsigned.crt.der new file mode 100644 index 0000000000000000000000000000000000000000..c28be8a26ea160d82f423cac7db60aafc31073f2 GIT binary patch literal 459 zcmXqLVmxlp#F)E)nTe5!i6?l`X>TFFwav9Frff3cV&l+i^EhYA!pvmgZYXRZ$i^JX z!py@}oSKtXoSB}Nnqnwmzy}iK=3y^MEiTE-OE(lW;0K9t@o)s>=Oh*d2m2Uu8*qYz z*o2wObsjzj7%&|&7;J5jX_*9D3^K`MH&dRv4h>u#0d2WGb1~*69bF+ znXsE>oN`e`cE=-i|10FZtNF<`vE;&9k*(h|lp>QR`8{R0p)C0SmHs7@*jTZ>T^)zA z%Xvf3c4_hbVmz_s?f=F02DSz~KrhJ(Gcx{XVKQIh_i z;?n*r>^HABG4ec3t?x;8TQ0m0Ib@kV7z|vQ6vR8`IIq1IrEl2y*WG+#g9qD_DGCSI zFN&S>!TZCE*v~VW6d68dYn))%oVMXy|9KX<1W`-oo%?MLiWzu^)T(@Aign%s08W>M Au>b%7 literal 0 HcmV?d00001 From 8a41468e95010d9d5bcf8d23a5acaab272777aa7 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 7 May 2026 18:20:39 +0200 Subject: [PATCH 02/36] Add CA:TRUE version of server5-selfsigned.crt Signed-off-by: Gilles Peskine --- data_files/Makefile | 12 ++++++++++++ data_files/server5-ca.der | Bin 0 -> 461 bytes 2 files changed, 12 insertions(+) create mode 100644 data_files/server5-ca.der diff --git a/data_files/Makefile b/data_files/Makefile index b8f1c39f2..7b39d031f 100644 --- a/data_files/Makefile +++ b/data_files/Makefile @@ -473,6 +473,18 @@ server5-selfsigned.crt.der: server5-selfsigned.crt $(OPENSSL) x509 -inform PEM -in $< -outform DER -out $@ all_final += server5-selfsigned.crt.der +server5-ca.der: server5.key + openssl req -x509 -key server5.key \ + -sha256 -days 3650 -nodes \ + -addext basicConstraints=critical,CA:TRUE \ + -addext keyUsage=critical,digitalSignature \ + -addext subjectKeyIdentifier=hash \ + -addext authorityKeyIdentifier=none \ + -set_serial 0x53a2cb4b124ead837da894b2 \ + -subj "/CN=selfsigned/OU=testing/O=PolarSSL/C=NL" \ + -outform DER -out $@ +all_final += server5-ca.der + # Create a certificate which is almost identical to "server3.crt", i.e. # it contains a public EC key and it is signed with RSA. The main difference # compared to "server3.crt" is that in this case we use a secp256r1 key ("server5.key") diff --git a/data_files/server5-ca.der b/data_files/server5-ca.der new file mode 100644 index 0000000000000000000000000000000000000000..1216a10dad70857f2b66d10d42f9a0feee10c896 GIT binary patch literal 461 zcmXqLVmxWk#8|L^nTe5!i6?l`X>TFFwav9Frff3cV&l+i^EhYA!pvmgZYXRZ$i^JX z!py@}oSKtXoSB}Nnqnwmzy}iK=3y^MEiTE-OE(lW;0K9t@o)s>=Oh*d2m2Uu8*qYz z*o2wU&zhn{{E4H_*<4|@v zZ|K=BExuojC$_x(zu3vZ0qjdzVMfOPEUX61KnffXviu+cV0f`N7{~(sEX&6t#v&4s zxU~NY`_1c3j66?M>wA*jmJ9Dg4qRq;1_Ku+1;4$EtREcX$c+s6ob}P+T=Xy5!~5LD yXL4009FVhUDF4W$z|~_sDRO#`dHjOV%~ew-%-(NdUFvWCKG^r(pY%zxstN#AA%^t; literal 0 HcmV?d00001 From 1090c7836330a030185d4131b8541b042b50e901 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 7 May 2026 18:37:04 +0200 Subject: [PATCH 03/36] Add test certificate that overflows basicConstraints This certificate is marlformed, but most implementations parse it as an empty basicConstraints (thus not a CA), whereas current Mbed TLS parses it as having basicConstraints containing CA:TRUE. Signed-off-by: Gilles Peskine --- data_files/Makefile | 18 ++++++++++++++++++ ...-constraints-sequence-overflow.badsign.crt | Bin 0 -> 461 bytes ...nstraints-sequence-overflow.selfsigned.crt | Bin 0 -> 347 bytes 3 files changed, 18 insertions(+) create mode 100644 data_files/parse_input/server5.basic-constraints-sequence-overflow.badsign.crt create mode 100644 data_files/parse_input/server5.basic-constraints-sequence-overflow.selfsigned.crt diff --git a/data_files/Makefile b/data_files/Makefile index 7b39d031f..0189ee49a 100644 --- a/data_files/Makefile +++ b/data_files/Makefile @@ -2310,6 +2310,24 @@ parse_input/server4.crt server4.crt: server4.key md=SHA256 version=3 output_file=$@ all_final += server4.crt +# Negative tests + +# Replace a well-formed basicConstraints extension containing CA:TRUE with +# a malformed extension that is empty with trailing junk. The trailing +# junk is a boolean with the value true, thus it would be interpreted as +# CA:TRUE if it was properly inside the basicConstraints extension. +parse_input/server5.basic-constraints-sequence-overflow.badsign.crt: server5-ca.der + perl -0777 -pe '$$n = s/\x04\x05\x30\x03\x01\x01\xff/\x04\x05\x30\x00\x01\x01\xff/g; die "More substitutions than expected" if $$n > 2; die "Substitution not found" if $$n < 1' <$< >$@.tmp + mv $@.tmp $@ +all_final: parse_input/server5.basic-constraints-sequence-overflow.badsign.crt + +parse_input/server5.basic-constraints-sequence-overflow.selfsigned.crt: parse_input/server5.basic-constraints-sequence-overflow.badsign.crt server5.key + $(OPENSSL) x509 -subj '/CN=Eve/' \ + -key server5.key \ + -days 3653 \ + -inform DER -in $< -outform DER -out $@ +all_final: parse_input/server5.basic-constraints-sequence-overflow.selfsigned.crt + # MD5 test certificate cert_md_test_key = $(cli_crt_key_file_rsa) diff --git a/data_files/parse_input/server5.basic-constraints-sequence-overflow.badsign.crt b/data_files/parse_input/server5.basic-constraints-sequence-overflow.badsign.crt new file mode 100644 index 0000000000000000000000000000000000000000..9b091ba98c00668a3f4d1dc1edb62d6a9836a28e GIT binary patch literal 461 zcmXqLVmxWk#8|L^nTe5!i6?l`X>TFFwav9Frff3cV&l+i^EhYA!pvmgZYXRZ$i^JX z!py@}oSKtXoSB}Nnqnwmzy}iK=3y^MEiTE-OE(lW;0K9t@o)s>=Oh*d2m2Uu8*qYz z*o2wU&zhn{{E4H_*<4|@v zZ|K=BExuojC$_x(zu3vZ0qjdzVMfOPEUX3$KnffXviu+cV0f`N7{~(sEX&6t#v&4s zxU~NY`_1c3j66?M>wA*jmJ9Dg4qRq;1_Ku+1;4$EtREcX$c+s6ob}P+T=Xy5!~5LD yXL4009FVhUDF4W$z|~_sDRO#`dHjOV%~ew-%-(NdUFvWCKG^r(pY%zxstN#8iH7n3 literal 0 HcmV?d00001 diff --git a/data_files/parse_input/server5.basic-constraints-sequence-overflow.selfsigned.crt b/data_files/parse_input/server5.basic-constraints-sequence-overflow.selfsigned.crt new file mode 100644 index 0000000000000000000000000000000000000000..a414bf51eb595ad56d7b8c20683d8f954c2d4f6f GIT binary patch literal 347 zcmXqLVhlHE{I`IaiIItkCwS3mZy~?6&9y70Y%<_t{|b5UYJRd!EV-~&Wb5|~rO2d7eoq;0C=337rGLpJHdbtJSI433a^BFh zU0Qs<7*A|@`+u>Mfr9}*&}p*5jEw(TSPd9}lmQ<|fFC3PbUk~6fh>s6$0Eie5|FsG z{|fuf>rIS2PgCoAlHHaI??d(~vj>BLE0ZF_j;uK$^0ik_uXxE{8gsrn|7Izpef5*) yU(Z)}EQ(1vFrP^w%QoumvKKwaZyd|?3KG9#E|st$qvG49SsMSo3O%r6{s#a;VRCo? literal 0 HcmV?d00001 From 303ef6a631ecf0735a9b33c1ca6fc444d7d22833 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 11 May 2026 11:14:04 +0200 Subject: [PATCH 04/36] Use the correct way of registering targets Fix some new targets missing from list and clean. Signed-off-by: Gilles Peskine --- data_files/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_files/Makefile b/data_files/Makefile index 0189ee49a..a0eec7d71 100644 --- a/data_files/Makefile +++ b/data_files/Makefile @@ -2319,14 +2319,14 @@ all_final += server4.crt parse_input/server5.basic-constraints-sequence-overflow.badsign.crt: server5-ca.der perl -0777 -pe '$$n = s/\x04\x05\x30\x03\x01\x01\xff/\x04\x05\x30\x00\x01\x01\xff/g; die "More substitutions than expected" if $$n > 2; die "Substitution not found" if $$n < 1' <$< >$@.tmp mv $@.tmp $@ -all_final: parse_input/server5.basic-constraints-sequence-overflow.badsign.crt +all_final += parse_input/server5.basic-constraints-sequence-overflow.badsign.crt parse_input/server5.basic-constraints-sequence-overflow.selfsigned.crt: parse_input/server5.basic-constraints-sequence-overflow.badsign.crt server5.key $(OPENSSL) x509 -subj '/CN=Eve/' \ -key server5.key \ -days 3653 \ -inform DER -in $< -outform DER -out $@ -all_final: parse_input/server5.basic-constraints-sequence-overflow.selfsigned.crt +all_final += parse_input/server5.basic-constraints-sequence-overflow.selfsigned.crt # MD5 test certificate From bc9d2191299314e87fb1d2837fee3e5cf08fd886 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 11 May 2026 11:14:52 +0200 Subject: [PATCH 05/36] Fix off-by-one error in sanity check Signed-off-by: Gilles Peskine --- data_files/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_files/Makefile b/data_files/Makefile index a0eec7d71..4a1b03f63 100644 --- a/data_files/Makefile +++ b/data_files/Makefile @@ -2317,7 +2317,7 @@ all_final += server4.crt # junk is a boolean with the value true, thus it would be interpreted as # CA:TRUE if it was properly inside the basicConstraints extension. parse_input/server5.basic-constraints-sequence-overflow.badsign.crt: server5-ca.der - perl -0777 -pe '$$n = s/\x04\x05\x30\x03\x01\x01\xff/\x04\x05\x30\x00\x01\x01\xff/g; die "More substitutions than expected" if $$n > 2; die "Substitution not found" if $$n < 1' <$< >$@.tmp + perl -0777 -pe '$$n = s/\x04\x05\x30\x03\x01\x01\xff/\x04\x05\x30\x00\x01\x01\xff/g; die "More substitutions than expected" if $$n > 1; die "Substitution not found" if $$n < 1' <$< >$@.tmp mv $@.tmp $@ all_final += parse_input/server5.basic-constraints-sequence-overflow.badsign.crt From 5b535a091b919dfe696485f9ce2c188c1f426438 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 12 May 2026 13:16:26 +0200 Subject: [PATCH 06/36] Add test certificate with basicConstraints containing only an INTEGER The basicConstraints extension contains only an INTEGER. According to RFC 5280, this should be cA=FALSE and a pathLenConstraint value which is effectively ignored. But Mbed TLS parses it as cA=TRUE if the integer value is nonzero. This certificate is syntactically valid, but RFC 5280 forbids CAs from emitting it. Compliant X.509 parsers treat it as cA=FALSE. Signed-off-by: Gilles Peskine --- data_files/Makefile | 16 ++++++++++++++++ ....basic-constraints-integer-first.badsign.crt | Bin 0 -> 461 bytes ...sic-constraints-integer-first.selfsigned.crt | Bin 0 -> 347 bytes 3 files changed, 16 insertions(+) create mode 100644 data_files/parse_input/server5.basic-constraints-integer-first.badsign.crt create mode 100644 data_files/parse_input/server5.basic-constraints-integer-first.selfsigned.crt diff --git a/data_files/Makefile b/data_files/Makefile index 4a1b03f63..5a379a99a 100644 --- a/data_files/Makefile +++ b/data_files/Makefile @@ -2328,6 +2328,22 @@ parse_input/server5.basic-constraints-sequence-overflow.selfsigned.crt: parse_in -inform DER -in $< -outform DER -out $@ all_final += parse_input/server5.basic-constraints-sequence-overflow.selfsigned.crt +# Replace a well-formed basicConstraints extension containing CA:TRUE with +# a malformed extension that is empty with trailing junk. The trailing +# junk is a boolean with the value true, thus it would be interpreted as +# CA:TRUE if it was properly inside the basicConstraints extension. +parse_input/server5.basic-constraints-integer-first.badsign.crt: server5-ca.der + perl -0777 -pe '$$n = s/\x04\x05\x30\x03\x01\x01\xff/\x04\x05\x30\x03\x02\x01\x01/g; die "More substitutions than expected" if $$n > 1; die "Substitution not found" if $$n < 1' <$< >$@.tmp + mv $@.tmp $@ +all_final += parse_input/server5.basic-constraints-integer-first.badsign.crt + +parse_input/server5.basic-constraints-integer-first.selfsigned.crt: parse_input/server5.basic-constraints-integer-first.badsign.crt server5.key + $(OPENSSL) x509 -subj '/CN=Eve/' \ + -key server5.key \ + -days 3653 \ + -inform DER -in $< -outform DER -out $@ +all_final += parse_input/server5.basic-constraints-integer-first.selfsigned.crt + # MD5 test certificate cert_md_test_key = $(cli_crt_key_file_rsa) diff --git a/data_files/parse_input/server5.basic-constraints-integer-first.badsign.crt b/data_files/parse_input/server5.basic-constraints-integer-first.badsign.crt new file mode 100644 index 0000000000000000000000000000000000000000..75d734a9a3a135c6c92aa791406ce642b0cb9272 GIT binary patch literal 461 zcmXqLVmxWk#8|L^nTe5!i6?l`X>TFFwav9Frff3cV&l+i^EhYA!pvmgZYXRZ$i^JX z!py@}oSKtXoSB}Nnqnwmzy}iK=3y^MEiTE-OE(lW;0K9t@o)s>=Oh*d2m2Uu8*qYz z*o2wU&zhn{{E4H_*<4|@v zZ|K=BExuojC$_x(zu3vZ0qjdzVMfOPEUX5=uww)Vge*Tu02p5E4FnzYq4k_a}Xltf~S4(W8a< literal 0 HcmV?d00001 diff --git a/data_files/parse_input/server5.basic-constraints-integer-first.selfsigned.crt b/data_files/parse_input/server5.basic-constraints-integer-first.selfsigned.crt new file mode 100644 index 0000000000000000000000000000000000000000..d57fe4a21e80f6ba9ffa3e18a112c1f6bb07ee5f GIT binary patch literal 347 zcmXqLVhlHE{I`IaiIItkCwS3mZy~?6&9y70Y%<_t{|b5UYJRd!EV-~&Wb5|~rO2d7eoq;0C=337rGLpJHdbtJSI433a^BFh zU0Qs<7*A|@`+u>Mfr9}*&}p*5jEw(TSPg(aVKm?a3GjmifUak6FpvfD`B=nQL;@0* z_FrMYdA*5|=V@wvPqN!`;eE(nW%gh&aAi_txK Date: Thu, 11 Jun 2026 16:58:48 +0200 Subject: [PATCH 07/36] data_files: makefile: remove duplicated "-out" parameter from PKCS7 generation Signed-off-by: Valerio Setti --- data_files/Makefile | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/data_files/Makefile b/data_files/Makefile index 5a379a99a..d138104ca 100644 --- a/data_files/Makefile +++ b/data_files/Makefile @@ -2438,7 +2438,7 @@ all_final += pkcs7-rsa-expired.crt # File with an otherwise valid signature signed with an expired cert pkcs7_data_rsa_expired.der: pkcs7-rsa-expired.key pkcs7-rsa-expired.crt pkcs7_data.bin - $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -inkey pkcs7-rsa-expired.key -signer pkcs7-rsa-expired.crt -noattr -outform DER -out $@ + $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -inkey pkcs7-rsa-expired.key -signer pkcs7-rsa-expired.crt -noattr -outform DER -out $@ all_final += pkcs7_data_rsa_expired.der # Convert signing certs to DER for testing PEM-free builds @@ -2461,42 +2461,42 @@ all_final += pkcs7_zerolendata_detached.der # pkcs7 signature file with CERT pkcs7_data_cert_signed_sha256.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) - $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@ + $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@ all_final += pkcs7_data_cert_signed_sha256.der # pkcs7 signature file with CERT and sha1 pkcs7_data_cert_signed_sha1.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) - $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha1 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@ + $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha1 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@ all_final += pkcs7_data_cert_signed_sha1.der # pkcs7 signature file with CERT and sha512 pkcs7_data_cert_signed_sha512.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) - $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha512 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@ + $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha512 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@ all_final += pkcs7_data_cert_signed_sha512.der # pkcs7 signature file without CERT pkcs7_data_without_cert_signed.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) - $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -signer pkcs7-rsa-sha256-1.pem -nocerts -noattr -outform DER -out $@ + $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -signer pkcs7-rsa-sha256-1.pem -nocerts -noattr -outform DER -out $@ all_final += pkcs7_data_without_cert_signed.der # pkcs7 signature file with signature pkcs7_data_with_signature.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) - $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -signer pkcs7-rsa-sha256-1.pem -nocerts -noattr -nodetach -outform DER -out $@ + $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -signer pkcs7-rsa-sha256-1.pem -nocerts -noattr -nodetach -outform DER -out $@ all_final += pkcs7_data_with_signature.der # pkcs7 signature file with two signers pkcs7_data_multiple_signed.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) $(pkcs7_test_cert_2) - $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -signer pkcs7-rsa-sha256-1.pem -signer pkcs7-rsa-sha256-2.pem -nocerts -noattr -outform DER -out $@ + $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -signer pkcs7-rsa-sha256-1.pem -signer pkcs7-rsa-sha256-2.pem -nocerts -noattr -outform DER -out $@ all_final += pkcs7_data_multiple_signed.der # pkcs7 signature file with three signers pkcs7_data_3_signed.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) $(pkcs7_test_cert_2) $(pkcs7_test_cert_3) - $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -signer pkcs7-rsa-sha256-1.pem -signer pkcs7-rsa-sha256-2.pem -signer pkcs7-rsa-sha256-3.pem -nocerts -noattr -outform DER -out $@ + $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -signer pkcs7-rsa-sha256-1.pem -signer pkcs7-rsa-sha256-2.pem -signer pkcs7-rsa-sha256-3.pem -nocerts -noattr -outform DER -out $@ all_final += pkcs7_data_3_signed.der # pkcs7 signature file with multiple certificates pkcs7_data_multiple_certs_signed.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) $(pkcs7_test_cert_2) - $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -signer pkcs7-rsa-sha256-1.pem -signer pkcs7-rsa-sha256-2.pem -noattr -outform DER -out $@ + $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -signer pkcs7-rsa-sha256-1.pem -signer pkcs7-rsa-sha256-2.pem -noattr -outform DER -out $@ all_final += pkcs7_data_multiple_certs_signed.der # pkcs7 signature file with corrupted CERT From c550de0d99e7221d5fcafe40622cc1883ba8422e Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Thu, 11 Jun 2026 17:13:59 +0200 Subject: [PATCH 08/36] data_files: pkcs7: add PKCS7 files using MD5 and RIPEMD160 Adjust the Makefile accordingly. Signed-off-by: Valerio Setti --- data_files/Makefile | 8 ++++++++ data_files/pkcs7_data_cert_signed_md5.der | Bin 0 -> 1282 bytes data_files/pkcs7_data_cert_signed_ripemd160.der | Bin 0 -> 1276 bytes 3 files changed, 8 insertions(+) create mode 100644 data_files/pkcs7_data_cert_signed_md5.der create mode 100644 data_files/pkcs7_data_cert_signed_ripemd160.der diff --git a/data_files/Makefile b/data_files/Makefile index d138104ca..b954d5626 100644 --- a/data_files/Makefile +++ b/data_files/Makefile @@ -2469,6 +2469,14 @@ pkcs7_data_cert_signed_sha1.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha1 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@ all_final += pkcs7_data_cert_signed_sha1.der +pkcs7_data_cert_signed_md5.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) + $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md md5 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@ +all_final += pkcs7_data_cert_signed_md5.der + +pkcs7_data_cert_signed_ripemd160.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) + $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md ripemd160 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@ +all_final += pkcs7_data_cert_signed_ripemd160.der + # pkcs7 signature file with CERT and sha512 pkcs7_data_cert_signed_sha512.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) $(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha512 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@ diff --git a/data_files/pkcs7_data_cert_signed_md5.der b/data_files/pkcs7_data_cert_signed_md5.der new file mode 100644 index 0000000000000000000000000000000000000000..1804ba0193c6937d91b08a5a3af2cd1769c8a8d9 GIT binary patch literal 1282 zcmXqLV)@6$snzDu_MMlJooPW6%X@<+me)*-jD~y$JZu~g875X%1_N${I>rS}%)SOq z%$^2KOoj`XnHZUvM3j@x3au6O+t8dgv-8xKw%cB(!XF#(!u2q61C1~-115*PFLjyxgL&GR> zUSnh~nWi=|Dj_?Mk(GhDiIJbdpox)-sfm%1Vb-G1vuT&tNX%KSxUyu0MZnXJ&|Ttx zPyG9u#=ri_Ny|0qy*sxlEZS8$$vx|->bd_nm(KR7<-hm3yiD`?qE;PmZX02i;9hkX z4x6Ljmk4vT>bD#`Vaoo}=}pl3n{nC&`im6$yGyTBep=}FiuDTrZC{=;$HT>H(GzTV z?(TIs$~x(;PUm9v9ZKa9zcp1NI?rsgucCR&BbI;sJd35%U4ZM_ zP7TqUORRSWy<;i1F}IcY9Lp~hA(HW+=ScR8KRyK?;N!8UM4e8ZZMX z2v=q$-BJa;EGvWCL9V7u4(An_+QpkGu>|SB86XOwbyH!_P@2>vs5Ji)ZR*y z{S)qQZgC4bcO~)W=D7zYxRY}H8guwN9%sMHo!kH4WJ=+L={W~~{yQMme&GJ5M+>>j z);9Msp`2;ktS)x^|JE?wvf%e}_b#Q-5W$zJTel>dS*-AT(kJ}7^isqW zxg`&E-{s%og})ppqH>S(8#XbP0h3-4Fyk5cke8xyWI@zSjFcgPDW0W?ks;ts z^gWR|8{IU*#DiAZ>=#{`#J1^_yU@{DW&c`wK4c#_wl&3H>qYdFKSisK395hhx1Rnv zsMpCtV8=$^PmSj*nGdTMCH>(&a>9%MOSimj(G7+w#$%Bz$L`nd^Jt%57wr64k=f*_ z%BPpVja?^K9XuDzd(TyP(u?TApl4sSSznsI%I95E|1{bwoNTPxD$GobtPBR+2t|wwnwWhJ znwUKenwSh10Hv9jM3j@x3au6O+t8dgv-8xKw%cB(!XF#(!u2q61C1~No5&o>!Y0h* z=VQocz{3XQaPhDPcsmE18;TkTgZRumJYc?pb81nEf}w$&IIoehfvJIop@E^Lp<$Fb zuQ4*0OjDZ}m5`kW^dWN-BR_*d6C)Q>6C)$TtVN+`(=M-(n6p}OWyuPQfTtayyTt#V z`1du9fBln_mTS^`cWzTyw5xKGd)8CcbN_EHo$XW0fA4j9ndb9FtvcS^Ho`2yz3MI; zHb=iN5$0&sZ#j6vl>MdCo1pbKlOanzC2})hl|ysC)n`Z z-Rp3ab<$m(&c*6Gl*%K1YpO)bnHJ6oe!eK`tANvWp4nz!Me~+NEdTg<7E7nQ0N1sh z8lpFsSnmva$5L!#ZY%LQmR~4BB;!HPk?a?Ld@TGXc-B~~^srqT5&USgp#}lkyGZLDI@B5(Z)o*cI@D6bLgi{%2t|UzC*}Ax=J0ns&VH9WxBtJ%l)?$qa}NIecR;HB!2L~+7IK%Z zZx9gLG;@oNGIQhhQrXH-0~U$5E2bXZ;_~;D)RL7k>G?ml^i)0U*O=t|&CdU}`SS-> zaY0sdH9B={eCn7&In%aTUF`V(tzo)l!SChnT}q)Lf-h6IZb>w=SmF1iPxy7|rHCnV zOCIXJ%fIoX`rMNXe>qM>J4+KIgWRUM z^8M4LTQ{%R7Z!9>s)>2YKEJI@ixzvA?mo1w=l!nev=_$RuW#M_*?Q9Pm|gR_)R5Vx zuX23Q8{6DzN*_`YAisy7d!e<_x4T9>)%$;KImUB*>diKGD4P6WVd-43fS+?~S zPa)Hd6A2Zru~Yf}_^erX= Date: Mon, 15 Jun 2026 16:23:49 +0200 Subject: [PATCH 09/36] scripts: add generate_ssl_session_reset_check.py This is the branch indipendent part of a script that is used in Mbed TLS to automatically generate the check code that verifies if 'mbedtls_session_reset()' function properly reset the 'mbedtls_ssl_context' structure. Signed-off-by: Valerio Setti --- .../generate_ssl_session_reset_check.py | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 scripts/mbedtls_framework/generate_ssl_session_reset_check.py diff --git a/scripts/mbedtls_framework/generate_ssl_session_reset_check.py b/scripts/mbedtls_framework/generate_ssl_session_reset_check.py new file mode 100644 index 000000000..598e6a076 --- /dev/null +++ b/scripts/mbedtls_framework/generate_ssl_session_reset_check.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python3 +"""Common code for generating the test code to validate mbedtls_ssl_session_reset(). +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +import enum +import os +import re +import sys +import typing +import argparse +from typing import Iterator, List, Tuple + +from . import c_parsing_helper +from . import typing_util +from . import build_tree + +class fields_info: + KEEP_FIELDS = [] + REALLOCATED_FIELDS = [] + IGNORE_FIELDS = [] + SPECIAL_FIELDS = {} + NAMED_STRUCTURES = [] + +class ResetBehavior(enum.Enum): + KEEP = 0 # Kept unchanged before/after the reset + RESET = 1 # Returned to the initial state (which is not necessarily 0) + REALLOCATE = 2, # Pointer that gets reallocated + IGNORE = 3, # Ignored field + +class CField(): + """Information about one field of a C struct.""" + name: str + conditional: str + reset_behavior: ResetBehavior + + def __init__(self, name: str, conditional: List[str]): + self.name = name + self.conditional = conditional + if name in fields_info.KEEP_FIELDS: + self.reset_behavior = ResetBehavior.KEEP + elif name in fields_info.REALLOCATED_FIELDS: + self.reset_behavior = ResetBehavior.REALLOCATE + elif name in fields_info.IGNORE_FIELDS: + self.reset_behavior = ResetBehavior.IGNORE + else: + self.reset_behavior = ResetBehavior.RESET + + def check_value(self) -> str: + if (self.reset_behavior == ResetBehavior.IGNORE): + return f'/* {self.name} is ignored */' + raise Exception(f'Reset behavior {self.reset_behavior} not allowed for {self.__class__.__name__} class') +class CScalar(CField): + def check_value(self) -> str: + if (self.reset_behavior == ResetBehavior.KEEP): + return f'TEST_EQUAL(before->{self.name}, after->{self.name});' + elif (self.reset_behavior == ResetBehavior.RESET): + return f'TEST_EQUAL(after->{self.name}, initial.{self.name});' + else: + return super().check_value() + +class CPointer(CScalar): + def check_value(self) -> str: + if (self.reset_behavior == ResetBehavior.REALLOCATE): + return f'TEST_ASSERT(after->{self.name} != NULL);' + else: + return super().check_value() + +class CArray(CField): + def check_value(self) -> str: + if (self.reset_behavior == ResetBehavior.KEEP): + return f'TEST_MEMORY_COMPARE(before->{self.name}, sizeof(before->{self.name}), after->{self.name}, sizeof(after->{self.name}));' + elif (self.reset_behavior == ResetBehavior.RESET): + return f'TEST_MEMORY_COMPARE(after->{self.name}, sizeof(after->{self.name}), initial.{self.name}, sizeof(initial.{self.name}));' + else: + super().check_value() + +class CStructure(CField): + def check_value(self) -> str: + if (self.reset_behavior == ResetBehavior.KEEP): + return f'TEST_MEMORY_COMPARE(&(before->{self.name}), sizeof(before->{self.name}), &(after->{self.name}), sizeof(after->{self.name}));' + elif (self.reset_behavior == ResetBehavior.RESET): + return f'TEST_MEMORY_COMPARE(&(after->{self.name}), sizeof(after->{self.name}), &(initial.{self.name}), sizeof(initial.{self.name}));' + else: + super().check_value() + +class CSpecial(CField): + def check_value(self) -> str: + return fields_info.SPECIAL_FIELDS[self.name] + +class CStruct: + """Information about the fields of a C struct.""" + + _PREPROCESSOR_RE = re.compile(r'\s*#\s*(\w+)\s*(.*)') + _STRUCT_RE = re.compile(r'struct\s+(\w+)\s*{') + _FIELD_RE = re.compile(r'\s*([^;]+);') + _PRIVATE_FIELD_RE = re.compile(r'MBEDTLS_PRIVATE\((\w+)\)') + _BARE_FIELD_RE = re.compile(r'[\t *](\w+)\Z') + _ARRAY_RE = re.compile(r'\[(\w+)\]') + _ANY_NON_SPACE_CHAR_RE = re.compile(r'\w+') + + def _parse_field(self, declaration: str, conditionals: List[str]) -> None: + # Note that this simplistic parsing finds fields in inline + # sub-structs, unions and enums. + m = self._PRIVATE_FIELD_RE.search(declaration) + if not m: + m = self._BARE_FIELD_RE.search(declaration) + if not m: + raise Exception(f'Field name not found in "{declaration}"') + name = m.group(1) + conditional = ' && '.join(conditionals) + # Check for special fields + if (name in fields_info.SPECIAL_FIELDS): + return CSpecial(name, conditional) + # Check for named structures + if (name in fields_info.NAMED_STRUCTURES): + return CStructure(name, conditional) + # Check for pointer + if ('*' in declaration): + return CPointer(name, conditional) + # Check for array + m = self._ARRAY_RE.search(declaration) + if m: + return CArray(name, conditional) + # If we get here then the field is a scalar + return CScalar(name, conditional) + + def _structure_fields(self, + lines: Iterator[Tuple[int, str]], + struct_name: str) -> Iterator[CField]: + found_start = False + for num, line in lines: + m = self._STRUCT_RE.match(line) + if m and m.group(1) == struct_name: + found_start = True + break + if not found_start: + raise Exception(f'Definition of struct {struct_name} not found') + conditionals: List[str] = [] + for num, line in lines: + if line.startswith('}'): + return + m = self._PREPROCESSOR_RE.match(line) + if m: + argument = m.group(2) + while line.endswith('\\'): + argument = argument[:-1] + ' ' + next(lines)[1] + directive = m.group(1) + if directive == 'if': + conditionals.append('(' + argument + ')') + elif directive == 'endif': + del conditionals[-1] + else: + raise Exception(f'Unsupported directive #{directive} at line {num}') + continue + m = self._FIELD_RE.match(line) + if m: + yield self._parse_field(m.group(1), conditionals) + continue + m = self._ANY_NON_SPACE_CHAR_RE.match(line) + if (m): + raise Exception(f'Failed to parse non-empty line {num}. Content is: {line}') + raise Exception(f'End of definition of struct {struct_name} not found') + + def __init__(self, file_name, struct_name) -> None: + """Parse a structure definition in a C source file.""" + with open(file_name) as input_file: + lines = c_parsing_helper.read_logical_lines(file_name) + self.fields = list(self._structure_fields(lines, struct_name)) + + +class SSLContextStruct(CStruct): + """Information about the fields of struct mbedtls_ssl_context.""" + + def __init__(self, out: typing_util.Writable) -> None: + self.out = out + super().__init__('include/mbedtls/ssl.h', 'mbedtls_ssl_context') + + def write_check_function(self) -> None: + out = self.out + out.write(f"""\ +/* + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ + +/* + * The following function was automatically generated through the script + * {sys.argv[0]}. + * Whenever a change is applied to "mbedtls_ssl_context" please re-run the + * script and update the function below accordingly. + * + * Please note that the script has some limitation: + * - Parsed structure must start with struct "mbedtls_ssl_context {{" + * (not "typedef struct {{"). + * - It must end with "}}" in column 0. + * - Must not contain "#else" or "#elif" conditionals. + * - Must not contain nested struct/union/enum definitions. + */ + +#include +#include +#include "mbedtls/psa_util.h" +#include + +#include + +#if defined(MBEDTLS_SSL_TLS_C) + +int mbedtls_test_ssl_check_context_after_session_reset(mbedtls_ssl_context *before, + mbedtls_ssl_context *after) +{{ + mbedtls_ssl_context initial; + int ret = -1; + + /* Create a freshly initialized SSL context*/ + memset(&initial, 0, sizeof(initial)); + mbedtls_ssl_init(&initial); + TEST_EQUAL(mbedtls_ssl_setup(&initial, after->conf), 0); + + /* *INDENT-OFF* */ +""") + for field in self.fields: + if field in fields_info.IGNORE_FIELDS: + continue + if field.conditional: + out.write(f'#if {field.conditional}\n') + out.write(f' {field.check_value()}\n') + if field.conditional: + out.write('#endif\n') + out.write(f"""\ + /* *INDENT-ON* */ + + ret = 0; + +exit: + mbedtls_ssl_free(&initial); + + return ret; +}} + +#endif /* MBEDTLS_SSL_TLS_C */ +""") + + +def main(): + if not build_tree.looks_like_mbedtls_root(os.curdir): + raise Exception("The script must be launched from the root path of Mbed TLS") + arg_parser = argparse.ArgumentParser() + arg_parser.add_argument('-o', dest='output_file', + help='Generated output file', + default='tests/src/ssl_context_reset_verifier.c') + parsed_args = arg_parser.parse_args() + + output_file = parsed_args.output_file + with open(output_file, 'wt') as out: + ssl_context = SSLContextStruct(out) + ssl_context.write_check_function() From 84174d094e58d24849d8ef810df09f3bc03b02b5 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 16 Jun 2026 13:15:03 +0200 Subject: [PATCH 10/36] scripts: fix pylint complains in generate_ssl_session_reset_check.py Signed-off-by: Valerio Setti --- .../generate_ssl_session_reset_check.py | 133 +++++++++++------- 1 file changed, 85 insertions(+), 48 deletions(-) diff --git a/scripts/mbedtls_framework/generate_ssl_session_reset_check.py b/scripts/mbedtls_framework/generate_ssl_session_reset_check.py index 598e6a076..31306fa4f 100644 --- a/scripts/mbedtls_framework/generate_ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/generate_ssl_session_reset_check.py @@ -11,86 +11,109 @@ import re import sys import typing import argparse -from typing import Iterator, List, Tuple +from typing import Dict, Iterator, List, Tuple from . import c_parsing_helper from . import typing_util from . import build_tree -class fields_info: - KEEP_FIELDS = [] - REALLOCATED_FIELDS = [] - IGNORE_FIELDS = [] - SPECIAL_FIELDS = {} - NAMED_STRUCTURES = [] +class FieldsInfo: + # pylint: disable=too-few-public-methods + """Defaul configuration of how each field of the structure must be handled. + This is meant to be overridden by the caller with branch-specific values. + """ + KEEP_FIELDS: List[str] = [] + REALLOCATED_FIELDS: List[str] = [] + IGNORE_FIELDS: List[str] = [] + SPECIAL_FIELDS: Dict[str, str] = {} + NAMED_STRUCTURES: List[str] = [] class ResetBehavior(enum.Enum): - KEEP = 0 # Kept unchanged before/after the reset - RESET = 1 # Returned to the initial state (which is not necessarily 0) - REALLOCATE = 2, # Pointer that gets reallocated - IGNORE = 3, # Ignored field + KEEP = 0 # Kept unchanged before/after the reset + RESET = 1 # Returned to the initial state (which is not necessarily 0) + REALLOCATE = 2 # Pointer that gets reallocated + IGNORE = 3 # Ignored field class CField(): + # pylint: disable=too-few-public-methods """Information about one field of a C struct.""" name: str conditional: str reset_behavior: ResetBehavior - def __init__(self, name: str, conditional: List[str]): + def __init__(self, name: str, conditional: str): self.name = name self.conditional = conditional - if name in fields_info.KEEP_FIELDS: + if name in FieldsInfo.KEEP_FIELDS: self.reset_behavior = ResetBehavior.KEEP - elif name in fields_info.REALLOCATED_FIELDS: + elif name in FieldsInfo.REALLOCATED_FIELDS: self.reset_behavior = ResetBehavior.REALLOCATE - elif name in fields_info.IGNORE_FIELDS: + elif name in FieldsInfo.IGNORE_FIELDS: self.reset_behavior = ResetBehavior.IGNORE else: self.reset_behavior = ResetBehavior.RESET def check_value(self) -> str: - if (self.reset_behavior == ResetBehavior.IGNORE): + if self.reset_behavior == ResetBehavior.IGNORE: return f'/* {self.name} is ignored */' - raise Exception(f'Reset behavior {self.reset_behavior} not allowed for {self.__class__.__name__} class') + raise Exception(f'Reset behavior {self.reset_behavior} not allowed ' + f'for {self.__class__.__name__} class') + class CScalar(CField): + # pylint: disable=too-few-public-methods + """Scalar field. Checked by value.""" def check_value(self) -> str: - if (self.reset_behavior == ResetBehavior.KEEP): + if self.reset_behavior == ResetBehavior.KEEP: return f'TEST_EQUAL(before->{self.name}, after->{self.name});' - elif (self.reset_behavior == ResetBehavior.RESET): + if self.reset_behavior == ResetBehavior.RESET: return f'TEST_EQUAL(after->{self.name}, initial.{self.name});' - else: - return super().check_value() + return super().check_value() class CPointer(CScalar): + # pylint: disable=too-few-public-methods + """Pointer field. Checked by value. They might be reallocated.""" def check_value(self) -> str: - if (self.reset_behavior == ResetBehavior.REALLOCATE): + if self.reset_behavior == ResetBehavior.REALLOCATE: return f'TEST_ASSERT(after->{self.name} != NULL);' - else: - return super().check_value() + return super().check_value() class CArray(CField): + # pylint: disable=too-few-public-methods + """Array field. Checked by memory comparison.""" def check_value(self) -> str: - if (self.reset_behavior == ResetBehavior.KEEP): - return f'TEST_MEMORY_COMPARE(before->{self.name}, sizeof(before->{self.name}), after->{self.name}, sizeof(after->{self.name}));' - elif (self.reset_behavior == ResetBehavior.RESET): - return f'TEST_MEMORY_COMPARE(after->{self.name}, sizeof(after->{self.name}), initial.{self.name}, sizeof(initial.{self.name}));' - else: - super().check_value() + if self.reset_behavior == ResetBehavior.KEEP: + return (f'TEST_MEMORY_COMPARE(before->{self.name}, ' + f'sizeof(before->{self.name}), after->{self.name}, ' + f'sizeof(after->{self.name}));') + if self.reset_behavior == ResetBehavior.RESET: + return (f'TEST_MEMORY_COMPARE(after->{self.name}, ' + f'sizeof(after->{self.name}), initial.{self.name}, ' + f'sizeof(initial.{self.name}));') + return super().check_value() class CStructure(CField): + # pylint: disable=too-few-public-methods + """Named structure field. Checked by memory comparison.""" def check_value(self) -> str: - if (self.reset_behavior == ResetBehavior.KEEP): - return f'TEST_MEMORY_COMPARE(&(before->{self.name}), sizeof(before->{self.name}), &(after->{self.name}), sizeof(after->{self.name}));' - elif (self.reset_behavior == ResetBehavior.RESET): - return f'TEST_MEMORY_COMPARE(&(after->{self.name}), sizeof(after->{self.name}), &(initial.{self.name}), sizeof(initial.{self.name}));' - else: - super().check_value() + if self.reset_behavior == ResetBehavior.KEEP: + return (f'TEST_MEMORY_COMPARE(&(before->{self.name}), ' + f'sizeof(before->{self.name}), &(after->{self.name}), ' + f'sizeof(after->{self.name}));') + if self.reset_behavior == ResetBehavior.RESET: + return (f'TEST_MEMORY_COMPARE(&(after->{self.name}), ' + f'sizeof(after->{self.name}), &(initial.{self.name}), ' + f'sizeof(initial.{self.name}));') + return super().check_value() class CSpecial(CField): + # pylint: disable=too-few-public-methods + """Field with a custom check. No behavior handing here because we know + what to expect from this field.""" def check_value(self) -> str: - return fields_info.SPECIAL_FIELDS[self.name] + return FieldsInfo.SPECIAL_FIELDS[self.name] class CStruct: + # pylint: disable=too-few-public-methods """Information about the fields of a C struct.""" _PREPROCESSOR_RE = re.compile(r'\s*#\s*(\w+)\s*(.*)') @@ -101,7 +124,8 @@ class CStruct: _ARRAY_RE = re.compile(r'\[(\w+)\]') _ANY_NON_SPACE_CHAR_RE = re.compile(r'\w+') - def _parse_field(self, declaration: str, conditionals: List[str]) -> None: + def _parse_field(self, declaration: str, conditionals: List[str]) -> CField: + """Return the CField object describing the given field declaration.""" # Note that this simplistic parsing finds fields in inline # sub-structs, unions and enums. m = self._PRIVATE_FIELD_RE.search(declaration) @@ -112,13 +136,13 @@ class CStruct: name = m.group(1) conditional = ' && '.join(conditionals) # Check for special fields - if (name in fields_info.SPECIAL_FIELDS): + if name in FieldsInfo.SPECIAL_FIELDS: return CSpecial(name, conditional) # Check for named structures - if (name in fields_info.NAMED_STRUCTURES): + if name in FieldsInfo.NAMED_STRUCTURES: return CStructure(name, conditional) # Check for pointer - if ('*' in declaration): + if '*' in declaration: return CPointer(name, conditional) # Check for array m = self._ARRAY_RE.search(declaration) @@ -127,9 +151,21 @@ class CStruct: # If we get here then the field is a scalar return CScalar(name, conditional) + @staticmethod + def _continue_parsing_preprocessor(argument: str, line: str, + lines: Iterator[Tuple[int, str]]) -> str: + """Append continuation lines of preprocesssor directive.""" + while line.endswith('\\'): + try: + argument = argument[:-1] + ' ' + next(lines)[1] + except StopIteration: + break + return argument + def _structure_fields(self, lines: Iterator[Tuple[int, str]], struct_name: str) -> Iterator[CField]: + """Yield a CField object for each field of the given structure.""" found_start = False for num, line in lines: m = self._STRUCT_RE.match(line) @@ -145,8 +181,8 @@ class CStruct: m = self._PREPROCESSOR_RE.match(line) if m: argument = m.group(2) - while line.endswith('\\'): - argument = argument[:-1] + ' ' + next(lines)[1] + if line.endswith('\\'): + argument = self._continue_parsing_preprocessor(argument, line, lines) directive = m.group(1) if directive == 'if': conditionals.append('(' + argument + ')') @@ -160,18 +196,18 @@ class CStruct: yield self._parse_field(m.group(1), conditionals) continue m = self._ANY_NON_SPACE_CHAR_RE.match(line) - if (m): + if m: raise Exception(f'Failed to parse non-empty line {num}. Content is: {line}') raise Exception(f'End of definition of struct {struct_name} not found') - def __init__(self, file_name, struct_name) -> None: + def __init__(self, file_name: str, struct_name: str) -> None: """Parse a structure definition in a C source file.""" - with open(file_name) as input_file: - lines = c_parsing_helper.read_logical_lines(file_name) + lines = c_parsing_helper.read_logical_lines(file_name) self.fields = list(self._structure_fields(lines, struct_name)) class SSLContextStruct(CStruct): + # pylint: disable=too-few-public-methods """Information about the fields of struct mbedtls_ssl_context.""" def __init__(self, out: typing_util.Writable) -> None: @@ -179,6 +215,7 @@ class SSLContextStruct(CStruct): super().__init__('include/mbedtls/ssl.h', 'mbedtls_ssl_context') def write_check_function(self) -> None: + """Write the generated context-checking function to the output.""" out = self.out out.write(f"""\ /* @@ -223,7 +260,7 @@ int mbedtls_test_ssl_check_context_after_session_reset(mbedtls_ssl_context *befo /* *INDENT-OFF* */ """) for field in self.fields: - if field in fields_info.IGNORE_FIELDS: + if field in FieldsInfo.IGNORE_FIELDS: continue if field.conditional: out.write(f'#if {field.conditional}\n') From b9d98cc78bacfb356a778c49515c3287a8a62662 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 16 Jun 2026 13:28:07 +0200 Subject: [PATCH 11/36] scripts: rename generate_ssl_session_reset_check.py The script cannot have the same name as the one used in Mbed TLS otherwise pylint will complain. Rename this one to 'ssl_session_reset_check.py'. Signed-off-by: Valerio Setti --- ...rate_ssl_session_reset_check.py => ssl_session_reset_check.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/mbedtls_framework/{generate_ssl_session_reset_check.py => ssl_session_reset_check.py} (100%) diff --git a/scripts/mbedtls_framework/generate_ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py similarity index 100% rename from scripts/mbedtls_framework/generate_ssl_session_reset_check.py rename to scripts/mbedtls_framework/ssl_session_reset_check.py From 02076a99e9983c3273514aea21ac19259069e3b7 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 16 Jun 2026 13:48:57 +0200 Subject: [PATCH 12/36] scripts: ssl_session_reset_check: use FieldsInfo instance from main branch Instead of extending the class in the mbedtls script, instantiate the framework class there and only extend the fields. Then pass the class to framework main() and use this instance there for computations. Signed-off-by: Valerio Setti --- .../ssl_session_reset_check.py | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index 31306fa4f..cc2553dcd 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -19,7 +19,7 @@ from . import build_tree class FieldsInfo: # pylint: disable=too-few-public-methods - """Defaul configuration of how each field of the structure must be handled. + """Default configuration of how each field of the structure must be handled. This is meant to be overridden by the caller with branch-specific values. """ KEEP_FIELDS: List[str] = [] @@ -40,15 +40,17 @@ class CField(): name: str conditional: str reset_behavior: ResetBehavior + fields_info: FieldsInfo - def __init__(self, name: str, conditional: str): + def __init__(self, name: str, conditional: str, fields_info: FieldsInfo): self.name = name self.conditional = conditional - if name in FieldsInfo.KEEP_FIELDS: + self.fields_info = fields_info + if name in fields_info.KEEP_FIELDS: self.reset_behavior = ResetBehavior.KEEP - elif name in FieldsInfo.REALLOCATED_FIELDS: + elif name in fields_info.REALLOCATED_FIELDS: self.reset_behavior = ResetBehavior.REALLOCATE - elif name in FieldsInfo.IGNORE_FIELDS: + elif name in fields_info.IGNORE_FIELDS: self.reset_behavior = ResetBehavior.IGNORE else: self.reset_behavior = ResetBehavior.RESET @@ -110,7 +112,7 @@ class CSpecial(CField): """Field with a custom check. No behavior handing here because we know what to expect from this field.""" def check_value(self) -> str: - return FieldsInfo.SPECIAL_FIELDS[self.name] + return self.fields_info.SPECIAL_FIELDS[self.name] class CStruct: # pylint: disable=too-few-public-methods @@ -136,20 +138,20 @@ class CStruct: name = m.group(1) conditional = ' && '.join(conditionals) # Check for special fields - if name in FieldsInfo.SPECIAL_FIELDS: - return CSpecial(name, conditional) + if name in self.fields_info.SPECIAL_FIELDS: + return CSpecial(name, conditional, self.fields_info) # Check for named structures - if name in FieldsInfo.NAMED_STRUCTURES: - return CStructure(name, conditional) + if name in self.fields_info.NAMED_STRUCTURES: + return CStructure(name, conditional, self.fields_info) # Check for pointer if '*' in declaration: - return CPointer(name, conditional) + return CPointer(name, conditional, self.fields_info) # Check for array m = self._ARRAY_RE.search(declaration) if m: - return CArray(name, conditional) + return CArray(name, conditional, self.fields_info) # If we get here then the field is a scalar - return CScalar(name, conditional) + return CScalar(name, conditional, self.fields_info) @staticmethod def _continue_parsing_preprocessor(argument: str, line: str, @@ -200,8 +202,10 @@ class CStruct: raise Exception(f'Failed to parse non-empty line {num}. Content is: {line}') raise Exception(f'End of definition of struct {struct_name} not found') - def __init__(self, file_name: str, struct_name: str) -> None: + def __init__(self, file_name: str, struct_name: str, + fields_info: FieldsInfo) -> None: """Parse a structure definition in a C source file.""" + self.fields_info = fields_info lines = c_parsing_helper.read_logical_lines(file_name) self.fields = list(self._structure_fields(lines, struct_name)) @@ -210,9 +214,11 @@ class SSLContextStruct(CStruct): # pylint: disable=too-few-public-methods """Information about the fields of struct mbedtls_ssl_context.""" - def __init__(self, out: typing_util.Writable) -> None: + def __init__(self, out: typing_util.Writable, + fields_info: FieldsInfo) -> None: self.out = out - super().__init__('include/mbedtls/ssl.h', 'mbedtls_ssl_context') + super().__init__('include/mbedtls/ssl.h', 'mbedtls_ssl_context', + fields_info) def write_check_function(self) -> None: """Write the generated context-checking function to the output.""" @@ -260,8 +266,6 @@ int mbedtls_test_ssl_check_context_after_session_reset(mbedtls_ssl_context *befo /* *INDENT-OFF* */ """) for field in self.fields: - if field in FieldsInfo.IGNORE_FIELDS: - continue if field.conditional: out.write(f'#if {field.conditional}\n') out.write(f' {field.check_value()}\n') @@ -282,7 +286,7 @@ exit: """) -def main(): +def main(fields_info: FieldsInfo): if not build_tree.looks_like_mbedtls_root(os.curdir): raise Exception("The script must be launched from the root path of Mbed TLS") arg_parser = argparse.ArgumentParser() @@ -293,5 +297,5 @@ def main(): output_file = parsed_args.output_file with open(output_file, 'wt') as out: - ssl_context = SSLContextStruct(out) + ssl_context = SSLContextStruct(out, fields_info) ssl_context.write_check_function() From 72d8c79e1d49620cd2d99e6ad81f46fd1980b613 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 16 Jun 2026 17:56:08 +0200 Subject: [PATCH 13/36] scripts: ssl_session_reset_check: use TEST_ASSERT instead of TEST_EQUAL TEST_EQUAL works fine on 64 bits platforms, but it has issues on 32 bits ones because it tries to cast 32 bits pointers to "unsigned long long" which is 64 bits and the compiler complains. Therefore switch to TEST_ASSERT that doesn't suffer from the same problem. Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index cc2553dcd..772b141a9 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -66,9 +66,9 @@ class CScalar(CField): """Scalar field. Checked by value.""" def check_value(self) -> str: if self.reset_behavior == ResetBehavior.KEEP: - return f'TEST_EQUAL(before->{self.name}, after->{self.name});' + return f'TEST_ASSERT(before->{self.name} == after->{self.name});' if self.reset_behavior == ResetBehavior.RESET: - return f'TEST_EQUAL(after->{self.name}, initial.{self.name});' + return f'TEST_ASSERT(after->{self.name} == initial.{self.name});' return super().check_value() class CPointer(CScalar): From 23c92d80d7108d694b5c25ebde96d56ff8962a06 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 16 Jun 2026 18:10:09 +0200 Subject: [PATCH 14/36] scrits: ssl_session_reset_check: move script limitations to ssl.h Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index 772b141a9..f8719366b 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -232,15 +232,6 @@ class SSLContextStruct(CStruct): /* * The following function was automatically generated through the script * {sys.argv[0]}. - * Whenever a change is applied to "mbedtls_ssl_context" please re-run the - * script and update the function below accordingly. - * - * Please note that the script has some limitation: - * - Parsed structure must start with struct "mbedtls_ssl_context {{" - * (not "typedef struct {{"). - * - It must end with "}}" in column 0. - * - Must not contain "#else" or "#elif" conditionals. - * - Must not contain nested struct/union/enum definitions. */ #include From 6db5390f23424d608c33363583bc94f5c85992b2 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Wed, 17 Jun 2026 09:26:04 +0200 Subject: [PATCH 15/36] scripts: ssl_session_reset_check: adjust regex for empty line Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index f8719366b..d2577e0d1 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -124,7 +124,7 @@ class CStruct: _PRIVATE_FIELD_RE = re.compile(r'MBEDTLS_PRIVATE\((\w+)\)') _BARE_FIELD_RE = re.compile(r'[\t *](\w+)\Z') _ARRAY_RE = re.compile(r'\[(\w+)\]') - _ANY_NON_SPACE_CHAR_RE = re.compile(r'\w+') + _NON_BLANK_RE = re.compile(r'.*\S') def _parse_field(self, declaration: str, conditionals: List[str]) -> CField: """Return the CField object describing the given field declaration.""" @@ -197,7 +197,7 @@ class CStruct: if m: yield self._parse_field(m.group(1), conditionals) continue - m = self._ANY_NON_SPACE_CHAR_RE.match(line) + m = self._NON_BLANK_RE.match(line) if m: raise Exception(f'Failed to parse non-empty line {num}. Content is: {line}') raise Exception(f'End of definition of struct {struct_name} not found') From 69ec61f1e895bf2b0e23735e6063d2db0e637ab0 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Wed, 17 Jun 2026 12:05:42 +0200 Subject: [PATCH 16/36] scripts: improve ssl_session_reset_check Remove FieldsInfo and introduce: - FieldsBehavior for the reset behaviors - NamedStructures type for the named structures Adjust classes for the above changes. Simplify CField removing if/elif in the __init__ function. Just use what has been put into 'self.reset_behavior'. CField.check_value now returns a List[str]. This is useful in case of special commands because some of them can be multiple lines. Signed-off-by: Valerio Setti --- .../ssl_session_reset_check.py | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index d2577e0d1..12c3b5341 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -17,102 +17,94 @@ from . import c_parsing_helper from . import typing_util from . import build_tree -class FieldsInfo: - # pylint: disable=too-few-public-methods - """Default configuration of how each field of the structure must be handled. - This is meant to be overridden by the caller with branch-specific values. - """ - KEEP_FIELDS: List[str] = [] - REALLOCATED_FIELDS: List[str] = [] - IGNORE_FIELDS: List[str] = [] - SPECIAL_FIELDS: Dict[str, str] = {} - NAMED_STRUCTURES: List[str] = [] - class ResetBehavior(enum.Enum): KEEP = 0 # Kept unchanged before/after the reset RESET = 1 # Returned to the initial state (which is not necessarily 0) REALLOCATE = 2 # Pointer that gets reallocated IGNORE = 3 # Ignored field +class FieldsBehavior(typing.NamedTuple): + """Expected reset behavior for the fields of the structure.""" + simple: Dict[str, ResetBehavior] + special: Dict[str, str] + +# The script isn't capable to identify named structures (ex: dtls_srtp_info) so +# it cannot instrument a proper check. Create a new type for them so that to +# simplify management. +NamedStructures = typing.NewType('NamedStructures', List[str]) + class CField(): # pylint: disable=too-few-public-methods """Information about one field of a C struct.""" name: str conditional: str reset_behavior: ResetBehavior - fields_info: FieldsInfo - def __init__(self, name: str, conditional: str, fields_info: FieldsInfo): + def __init__(self, name: str, conditional: str, reset_behavior: ResetBehavior | str): self.name = name self.conditional = conditional - self.fields_info = fields_info - if name in fields_info.KEEP_FIELDS: - self.reset_behavior = ResetBehavior.KEEP - elif name in fields_info.REALLOCATED_FIELDS: - self.reset_behavior = ResetBehavior.REALLOCATE - elif name in fields_info.IGNORE_FIELDS: - self.reset_behavior = ResetBehavior.IGNORE - else: - self.reset_behavior = ResetBehavior.RESET + self.reset_behavior = reset_behavior - def check_value(self) -> str: + def check_value(self) -> List[str]: if self.reset_behavior == ResetBehavior.IGNORE: - return f'/* {self.name} is ignored */' + return [f'/* {self.name} is ignored */'] raise Exception(f'Reset behavior {self.reset_behavior} not allowed ' f'for {self.__class__.__name__} class') class CScalar(CField): # pylint: disable=too-few-public-methods """Scalar field. Checked by value.""" - def check_value(self) -> str: + def check_value(self) -> List[str]: if self.reset_behavior == ResetBehavior.KEEP: - return f'TEST_ASSERT(before->{self.name} == after->{self.name});' + return [f'TEST_ASSERT(before->{self.name} == after->{self.name});'] if self.reset_behavior == ResetBehavior.RESET: - return f'TEST_ASSERT(after->{self.name} == initial.{self.name});' + return [f'TEST_ASSERT(after->{self.name} == initial.{self.name});'] return super().check_value() class CPointer(CScalar): # pylint: disable=too-few-public-methods """Pointer field. Checked by value. They might be reallocated.""" - def check_value(self) -> str: + def check_value(self) -> List[str]: if self.reset_behavior == ResetBehavior.REALLOCATE: - return f'TEST_ASSERT(after->{self.name} != NULL);' + return [f'TEST_ASSERT(after->{self.name} != NULL);'] return super().check_value() class CArray(CField): # pylint: disable=too-few-public-methods """Array field. Checked by memory comparison.""" - def check_value(self) -> str: + def check_value(self) -> List[str]: if self.reset_behavior == ResetBehavior.KEEP: - return (f'TEST_MEMORY_COMPARE(before->{self.name}, ' + return [f'TEST_MEMORY_COMPARE(before->{self.name}, ' f'sizeof(before->{self.name}), after->{self.name}, ' - f'sizeof(after->{self.name}));') + f'sizeof(after->{self.name}));'] if self.reset_behavior == ResetBehavior.RESET: - return (f'TEST_MEMORY_COMPARE(after->{self.name}, ' + return [f'TEST_MEMORY_COMPARE(after->{self.name}, ' f'sizeof(after->{self.name}), initial.{self.name}, ' - f'sizeof(initial.{self.name}));') + f'sizeof(initial.{self.name}));'] return super().check_value() class CStructure(CField): # pylint: disable=too-few-public-methods """Named structure field. Checked by memory comparison.""" - def check_value(self) -> str: + def check_value(self) -> List[str]: if self.reset_behavior == ResetBehavior.KEEP: - return (f'TEST_MEMORY_COMPARE(&(before->{self.name}), ' + return [f'TEST_MEMORY_COMPARE(&(before->{self.name}), ' f'sizeof(before->{self.name}), &(after->{self.name}), ' - f'sizeof(after->{self.name}));') + f'sizeof(after->{self.name}));'] if self.reset_behavior == ResetBehavior.RESET: - return (f'TEST_MEMORY_COMPARE(&(after->{self.name}), ' + return [f'TEST_MEMORY_COMPARE(&(after->{self.name}), ' f'sizeof(after->{self.name}), &(initial.{self.name}), ' - f'sizeof(initial.{self.name}));') + f'sizeof(initial.{self.name}));'] return super().check_value() class CSpecial(CField): # pylint: disable=too-few-public-methods """Field with a custom check. No behavior handing here because we know - what to expect from this field.""" - def check_value(self) -> str: - return self.fields_info.SPECIAL_FIELDS[self.name] + what to expect from this field. + reset_behavior in this case it's a list of strings, so the only thing we + need to do is to print them one per line.""" + def check_value(self) -> List[str]: + return self.reset_behavior class CStruct: # pylint: disable=too-few-public-methods @@ -138,20 +130,23 @@ class CStruct: name = m.group(1) conditional = ' && '.join(conditionals) # Check for special fields - if name in self.fields_info.SPECIAL_FIELDS: - return CSpecial(name, conditional, self.fields_info) + if name in self.fields_behavior.special: + return CSpecial(name, conditional, self.fields_behavior.special[name]) + # Check if name is in the 'simple' behavior dictionary + behavior = self.fields_behavior.simple[name] if name in self.fields_behavior.simple \ + else ResetBehavior.RESET # Check for named structures - if name in self.fields_info.NAMED_STRUCTURES: - return CStructure(name, conditional, self.fields_info) + if name in self.named_structures: + return CStructure(name, conditional, behavior) # Check for pointer if '*' in declaration: - return CPointer(name, conditional, self.fields_info) + return CPointer(name, conditional, behavior) # Check for array m = self._ARRAY_RE.search(declaration) if m: - return CArray(name, conditional, self.fields_info) + return CArray(name, conditional, behavior) # If we get here then the field is a scalar - return CScalar(name, conditional, self.fields_info) + return CScalar(name, conditional, behavior) @staticmethod def _continue_parsing_preprocessor(argument: str, line: str, @@ -203,9 +198,11 @@ class CStruct: raise Exception(f'End of definition of struct {struct_name} not found') def __init__(self, file_name: str, struct_name: str, - fields_info: FieldsInfo) -> None: + fields_behavior: FieldsBehavior, + named_structures: NamedStructures) -> None: """Parse a structure definition in a C source file.""" - self.fields_info = fields_info + self.fields_behavior = fields_behavior + self.named_structures = named_structures lines = c_parsing_helper.read_logical_lines(file_name) self.fields = list(self._structure_fields(lines, struct_name)) @@ -215,10 +212,11 @@ class SSLContextStruct(CStruct): """Information about the fields of struct mbedtls_ssl_context.""" def __init__(self, out: typing_util.Writable, - fields_info: FieldsInfo) -> None: + fields_info: FieldsBehavior, + named_structures: NamedStructures) -> None: self.out = out super().__init__('include/mbedtls/ssl.h', 'mbedtls_ssl_context', - fields_info) + fields_info, named_structures) def write_check_function(self) -> None: """Write the generated context-checking function to the output.""" @@ -259,7 +257,8 @@ int mbedtls_test_ssl_check_context_after_session_reset(mbedtls_ssl_context *befo for field in self.fields: if field.conditional: out.write(f'#if {field.conditional}\n') - out.write(f' {field.check_value()}\n') + for check_line in field.check_value(): + out.write(f' {check_line}\n') if field.conditional: out.write('#endif\n') out.write(f"""\ @@ -277,7 +276,8 @@ exit: """) -def main(fields_info: FieldsInfo): +def main(fields_behavior: FieldsBehavior, + named_structures: NamedStructures): if not build_tree.looks_like_mbedtls_root(os.curdir): raise Exception("The script must be launched from the root path of Mbed TLS") arg_parser = argparse.ArgumentParser() @@ -288,5 +288,5 @@ def main(fields_info: FieldsInfo): output_file = parsed_args.output_file with open(output_file, 'wt') as out: - ssl_context = SSLContextStruct(out, fields_info) + ssl_context = SSLContextStruct(out, fields_behavior, named_structures) ssl_context.write_check_function() From 0a8cc3eead89ed8b37dd697273c8d9eacf20b7cf Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Wed, 17 Jun 2026 14:29:39 +0200 Subject: [PATCH 17/36] scripts: ssl_session_reset_check: change class logic Instead of using classes to differentiate between elements type inside the structure (scalar, pointer, etc) use classes to differentiate the type of the check (keep, reset, etc). Signed-off-by: Valerio Setti --- .../ssl_session_reset_check.py | 158 +++++++++++------- 1 file changed, 94 insertions(+), 64 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index 12c3b5341..fe2bce87d 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -22,10 +22,19 @@ class ResetBehavior(enum.Enum): RESET = 1 # Returned to the initial state (which is not necessarily 0) REALLOCATE = 2 # Pointer that gets reallocated IGNORE = 3 # Ignored field + SPECIAL = 4 + +class ElementType(enum.Enum): + SCALAR = 0, + POINTER = 1, + ARRAY = 2, + NAMED_STRUCTURE = 3, + IGNORE = 4, + SPECIAL = 5, class FieldsBehavior(typing.NamedTuple): """Expected reset behavior for the fields of the structure.""" - simple: Dict[str, ResetBehavior] + rules: Dict[str, ResetBehavior] special: Dict[str, str] # The script isn't capable to identify named structures (ex: dtls_srtp_info) so @@ -38,73 +47,75 @@ class CField(): """Information about one field of a C struct.""" name: str conditional: str - reset_behavior: ResetBehavior + element_type: ElementType - def __init__(self, name: str, conditional: str, reset_behavior: ResetBehavior | str): + def __init__(self, name: str, conditional: str, element_type: ElementType): self.name = name self.conditional = conditional - self.reset_behavior = reset_behavior + self.element_type = element_type def check_value(self) -> List[str]: - if self.reset_behavior == ResetBehavior.IGNORE: - return [f'/* {self.name} is ignored */'] - raise Exception(f'Reset behavior {self.reset_behavior} not allowed ' - f'for {self.__class__.__name__} class') + raise Exception(f'Class {self.__class__.__name__} cannot handle entries' + f'of type {self.element_type}') -class CScalar(CField): +class CFieldIgnore(CField): # pylint: disable=too-few-public-methods - """Scalar field. Checked by value.""" + """Explicitly ignored field.""" def check_value(self) -> List[str]: - if self.reset_behavior == ResetBehavior.KEEP: + return [f'/* {self.name} is ignored */'] + +class CFieldKeep(CField): + # pylint: disable=too-few-public-methods + """Field kept unchanged.""" + def check_value(self) -> List[str]: + if (self.element_type == ElementType.SCALAR) or (self.element_type == ElementType.POINTER): return [f'TEST_ASSERT(before->{self.name} == after->{self.name});'] - if self.reset_behavior == ResetBehavior.RESET: - return [f'TEST_ASSERT(after->{self.name} == initial.{self.name});'] - return super().check_value() - -class CPointer(CScalar): - # pylint: disable=too-few-public-methods - """Pointer field. Checked by value. They might be reallocated.""" - def check_value(self) -> List[str]: - if self.reset_behavior == ResetBehavior.REALLOCATE: - return [f'TEST_ASSERT(after->{self.name} != NULL);'] - return super().check_value() - -class CArray(CField): - # pylint: disable=too-few-public-methods - """Array field. Checked by memory comparison.""" - def check_value(self) -> List[str]: - if self.reset_behavior == ResetBehavior.KEEP: + elif self.element_type == ElementType.ARRAY: return [f'TEST_MEMORY_COMPARE(before->{self.name}, ' f'sizeof(before->{self.name}), after->{self.name}, ' f'sizeof(after->{self.name}));'] - if self.reset_behavior == ResetBehavior.RESET: + elif self.element_type == ElementType.NAMED_STRUCTURE: + return [f'TEST_MEMORY_COMPARE(&(before->{self.name}), ' + f'sizeof(before->{self.name}), &(after.{self.name}), ' + f'sizeof(after.{self.name}));'] + return super().check_value() + +class CFieldReset(CField): + # pylint: disable=too-few-public-methods + """Field returned to the intial state.""" + def check_value(self) -> List[str]: + if (self.element_type == ElementType.SCALAR) or (self.element_type == ElementType.POINTER): + return [f'TEST_ASSERT(after->{self.name} == initial.{self.name});'] + elif self.element_type == ElementType.ARRAY: return [f'TEST_MEMORY_COMPARE(after->{self.name}, ' f'sizeof(after->{self.name}), initial.{self.name}, ' f'sizeof(initial.{self.name}));'] - return super().check_value() - -class CStructure(CField): - # pylint: disable=too-few-public-methods - """Named structure field. Checked by memory comparison.""" - def check_value(self) -> List[str]: - if self.reset_behavior == ResetBehavior.KEEP: - return [f'TEST_MEMORY_COMPARE(&(before->{self.name}), ' - f'sizeof(before->{self.name}), &(after->{self.name}), ' - f'sizeof(after->{self.name}));'] - if self.reset_behavior == ResetBehavior.RESET: + elif self.element_type == ElementType.NAMED_STRUCTURE: return [f'TEST_MEMORY_COMPARE(&(after->{self.name}), ' f'sizeof(after->{self.name}), &(initial.{self.name}), ' f'sizeof(initial.{self.name}));'] return super().check_value() -class CSpecial(CField): +class CFieldReallocate(CField): # pylint: disable=too-few-public-methods - """Field with a custom check. No behavior handing here because we know - what to expect from this field. - reset_behavior in this case it's a list of strings, so the only thing we - need to do is to print them one per line.""" + """Pointer (might be) reallocated during reset.""" def check_value(self) -> List[str]: - return self.reset_behavior + if (self.element_type == ElementType.POINTER): + return [f'TEST_ASSERT(after->{self.name} != NULL);'] + return super().check_value() + +class CFieldSpecial(CField): + # pylint: disable=too-few-public-methods + """Field with a custom check.""" + custom_behavior: ResetBehavior + + def __init__(self, name: str, conditional: str, element_type: ElementType, + custom_behavior: List[str] = None): + self.custom_behavior = custom_behavior + super().__init__(name,conditional, element_type) + + def check_value(self) -> List[str]: + return self.custom_behavior class CStruct: # pylint: disable=too-few-public-methods @@ -118,6 +129,24 @@ class CStruct: _ARRAY_RE = re.compile(r'\[(\w+)\]') _NON_BLANK_RE = re.compile(r'.*\S') + def _get_element_type(self, name: str, declaration: str) -> ElementType: + # Check for fields with custom check rules + if (name in self.fields_behavior.special) and \ + (self.fields_behavior.special[name] == ResetBehavior.SPECIAL): + return ElementType.SPECIAL + # Check for named structures + if name in self.named_structures: + return ElementType.NAMED_STRUCTURE + # Check for pointer + if '*' in declaration: + return ElementType.POINTER + # Check for array + m = self._ARRAY_RE.search(declaration) + if m: + return ElementType.ARRAY + # If we get here then the field is a scalar + return ElementType.SCALAR + def _parse_field(self, declaration: str, conditionals: List[str]) -> CField: """Return the CField object describing the given field declaration.""" # Note that this simplistic parsing finds fields in inline @@ -129,24 +158,25 @@ class CStruct: raise Exception(f'Field name not found in "{declaration}"') name = m.group(1) conditional = ' && '.join(conditionals) - # Check for special fields - if name in self.fields_behavior.special: - return CSpecial(name, conditional, self.fields_behavior.special[name]) - # Check if name is in the 'simple' behavior dictionary - behavior = self.fields_behavior.simple[name] if name in self.fields_behavior.simple \ - else ResetBehavior.RESET - # Check for named structures - if name in self.named_structures: - return CStructure(name, conditional, behavior) - # Check for pointer - if '*' in declaration: - return CPointer(name, conditional, behavior) - # Check for array - m = self._ARRAY_RE.search(declaration) - if m: - return CArray(name, conditional, behavior) - # If we get here then the field is a scalar - return CScalar(name, conditional, behavior) + # Get the expected behavior on reset or use RESET by default. + if name in self.fields_behavior.rules: + behavior = self.fields_behavior.rules[name] + else: + behavior = ResetBehavior.RESET + element_type = self._get_element_type(name, declaration) + if behavior == ResetBehavior.SPECIAL: + if (name not in self.fields_behavior.special): + raise Exception(f'Field {name} was given a SPECIAL behavior, but' + f'the custom check rule has not been defined') + return CFieldSpecial(name, conditional, element_type, + self.fields_behavior.special[name]) + elif behavior == ResetBehavior.KEEP: + return CFieldKeep(name, conditional, element_type) + elif behavior == ResetBehavior.REALLOCATE: + return CFieldReallocate(name, conditional, element_type) + elif behavior == ResetBehavior.IGNORE: + return CFieldIgnore(name, conditional, element_type) + return CFieldReset(name, conditional, element_type) @staticmethod def _continue_parsing_preprocessor(argument: str, line: str, From 4fd29e6dd592aadf5e462425bdd8f70037066690 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Wed, 17 Jun 2026 14:37:58 +0200 Subject: [PATCH 18/36] scripts: ssl_session_reset_check: minor improvements - Rename FieldsBehavior to FieldsInfo - Move named_structure to FieldsInfo Adjust the script as need for this change. Signed-off-by: Valerio Setti --- .../ssl_session_reset_check.py | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index fe2bce87d..29462c694 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -32,15 +32,13 @@ class ElementType(enum.Enum): IGNORE = 4, SPECIAL = 5, -class FieldsBehavior(typing.NamedTuple): +class FieldsInfo(typing.NamedTuple): """Expected reset behavior for the fields of the structure.""" rules: Dict[str, ResetBehavior] special: Dict[str, str] - -# The script isn't capable to identify named structures (ex: dtls_srtp_info) so -# it cannot instrument a proper check. Create a new type for them so that to -# simplify management. -NamedStructures = typing.NewType('NamedStructures', List[str]) + # The script isn't capable to identify named structures (ex: dtls_srtp_info) + # so we keep an explicit list of them. + named_structures: List[str] class CField(): # pylint: disable=too-few-public-methods @@ -131,11 +129,11 @@ class CStruct: def _get_element_type(self, name: str, declaration: str) -> ElementType: # Check for fields with custom check rules - if (name in self.fields_behavior.special) and \ - (self.fields_behavior.special[name] == ResetBehavior.SPECIAL): + if (name in self.fields_info.special) and \ + (self.fields_info.special[name] == ResetBehavior.SPECIAL): return ElementType.SPECIAL # Check for named structures - if name in self.named_structures: + if name in self.fields_info.named_structures: return ElementType.NAMED_STRUCTURE # Check for pointer if '*' in declaration: @@ -159,17 +157,17 @@ class CStruct: name = m.group(1) conditional = ' && '.join(conditionals) # Get the expected behavior on reset or use RESET by default. - if name in self.fields_behavior.rules: - behavior = self.fields_behavior.rules[name] + if name in self.fields_info.rules: + behavior = self.fields_info.rules[name] else: behavior = ResetBehavior.RESET element_type = self._get_element_type(name, declaration) if behavior == ResetBehavior.SPECIAL: - if (name not in self.fields_behavior.special): + if (name not in self.fields_info.special): raise Exception(f'Field {name} was given a SPECIAL behavior, but' f'the custom check rule has not been defined') return CFieldSpecial(name, conditional, element_type, - self.fields_behavior.special[name]) + self.fields_info.special[name]) elif behavior == ResetBehavior.KEEP: return CFieldKeep(name, conditional, element_type) elif behavior == ResetBehavior.REALLOCATE: @@ -228,11 +226,9 @@ class CStruct: raise Exception(f'End of definition of struct {struct_name} not found') def __init__(self, file_name: str, struct_name: str, - fields_behavior: FieldsBehavior, - named_structures: NamedStructures) -> None: + fields_info: FieldsInfo) -> None: """Parse a structure definition in a C source file.""" - self.fields_behavior = fields_behavior - self.named_structures = named_structures + self.fields_info = fields_info lines = c_parsing_helper.read_logical_lines(file_name) self.fields = list(self._structure_fields(lines, struct_name)) @@ -242,11 +238,10 @@ class SSLContextStruct(CStruct): """Information about the fields of struct mbedtls_ssl_context.""" def __init__(self, out: typing_util.Writable, - fields_info: FieldsBehavior, - named_structures: NamedStructures) -> None: + fields_info: FieldsInfo) -> None: self.out = out super().__init__('include/mbedtls/ssl.h', 'mbedtls_ssl_context', - fields_info, named_structures) + fields_info) def write_check_function(self) -> None: """Write the generated context-checking function to the output.""" @@ -306,8 +301,7 @@ exit: """) -def main(fields_behavior: FieldsBehavior, - named_structures: NamedStructures): +def main(fields_info: FieldsInfo): if not build_tree.looks_like_mbedtls_root(os.curdir): raise Exception("The script must be launched from the root path of Mbed TLS") arg_parser = argparse.ArgumentParser() @@ -318,5 +312,5 @@ def main(fields_behavior: FieldsBehavior, output_file = parsed_args.output_file with open(output_file, 'wt') as out: - ssl_context = SSLContextStruct(out, fields_behavior, named_structures) + ssl_context = SSLContextStruct(out, fields_info) ssl_context.write_check_function() From 22f164bada09a2da38f2b2b552b9e5f2249348a9 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Wed, 17 Jun 2026 15:11:43 +0200 Subject: [PATCH 19/36] scripts: ssl_session_reset_check: use const pointers for arguments in generated function Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index 29462c694..2b101cce7 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -266,8 +266,8 @@ class SSLContextStruct(CStruct): #if defined(MBEDTLS_SSL_TLS_C) -int mbedtls_test_ssl_check_context_after_session_reset(mbedtls_ssl_context *before, - mbedtls_ssl_context *after) +int mbedtls_test_ssl_check_context_after_session_reset(const mbedtls_ssl_context *before, + const mbedtls_ssl_context *after) {{ mbedtls_ssl_context initial; int ret = -1; From 718ac30fd393f14fb2ad6d024f459da68f1a5172 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Thu, 18 Jun 2026 18:09:01 +0200 Subject: [PATCH 20/36] scripts: ssl_session_reset_check: remove shebang line Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index 2b101cce7..bcf2b3859 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """Common code for generating the test code to validate mbedtls_ssl_session_reset(). """ From 16349fe394399cec10e54fe9a85df9152a2303e8 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Fri, 19 Jun 2026 11:04:03 +0200 Subject: [PATCH 21/36] scripts: ssl_session_reset_check: address pylint complains Signed-off-by: Valerio Setti --- .../ssl_session_reset_check.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index bcf2b3859..cb3eb4795 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -24,17 +24,17 @@ class ResetBehavior(enum.Enum): SPECIAL = 4 class ElementType(enum.Enum): - SCALAR = 0, - POINTER = 1, - ARRAY = 2, - NAMED_STRUCTURE = 3, - IGNORE = 4, - SPECIAL = 5, + SCALAR = 0 + POINTER = 1 + ARRAY = 2 + NAMED_STRUCTURE = 3 + IGNORE = 4 + SPECIAL = 5 class FieldsInfo(typing.NamedTuple): """Expected reset behavior for the fields of the structure.""" rules: Dict[str, ResetBehavior] - special: Dict[str, str] + special: Dict[str, List[str]] # The script isn't capable to identify named structures (ex: dtls_srtp_info) # so we keep an explicit list of them. named_structures: List[str] @@ -97,19 +97,19 @@ class CFieldReallocate(CField): # pylint: disable=too-few-public-methods """Pointer (might be) reallocated during reset.""" def check_value(self) -> List[str]: - if (self.element_type == ElementType.POINTER): + if self.element_type == ElementType.POINTER: return [f'TEST_ASSERT(after->{self.name} != NULL);'] return super().check_value() class CFieldSpecial(CField): # pylint: disable=too-few-public-methods """Field with a custom check.""" - custom_behavior: ResetBehavior + custom_behavior: List[str] def __init__(self, name: str, conditional: str, element_type: ElementType, - custom_behavior: List[str] = None): + custom_behavior: List[str]): self.custom_behavior = custom_behavior - super().__init__(name,conditional, element_type) + super().__init__(name, conditional, element_type) def check_value(self) -> List[str]: return self.custom_behavior @@ -127,6 +127,8 @@ class CStruct: _NON_BLANK_RE = re.compile(r'.*\S') def _get_element_type(self, name: str, declaration: str) -> ElementType: + """Return structure field type based on either its name or the fact + that it belongs to the list of special symbols/named structures""" # Check for fields with custom check rules if (name in self.fields_info.special) and \ (self.fields_info.special[name] == ResetBehavior.SPECIAL): @@ -162,7 +164,7 @@ class CStruct: behavior = ResetBehavior.RESET element_type = self._get_element_type(name, declaration) if behavior == ResetBehavior.SPECIAL: - if (name not in self.fields_info.special): + if name not in self.fields_info.special: raise Exception(f'Field {name} was given a SPECIAL behavior, but' f'the custom check rule has not been defined') return CFieldSpecial(name, conditional, element_type, From f3ece31c7b2b3ff03c4d804f51429202908611c5 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 09:31:50 +0200 Subject: [PATCH 22/36] scripts: ssl_session_reset_check: use TEST_EQUAL for scalars Keep TEST_ASSERT() for pointers, but switch to TEST_EQUAL for scalars. Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index cb3eb4795..d17815d23 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -65,7 +65,9 @@ class CFieldKeep(CField): # pylint: disable=too-few-public-methods """Field kept unchanged.""" def check_value(self) -> List[str]: - if (self.element_type == ElementType.SCALAR) or (self.element_type == ElementType.POINTER): + if (self.element_type == ElementType.SCALAR): + return [f'TEST_EQUAL(before->{self.name}, after->{self.name});'] + elif (self.element_type == ElementType.POINTER): return [f'TEST_ASSERT(before->{self.name} == after->{self.name});'] elif self.element_type == ElementType.ARRAY: return [f'TEST_MEMORY_COMPARE(before->{self.name}, ' From 203de77d2b02185eb5adce9b0cddbe7ad3f5e267 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 09:35:03 +0200 Subject: [PATCH 23/36] scripts: ssl_session_reset_check: make named_structures a FrozenSet[str] Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index d17815d23..7e8c98303 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -10,7 +10,7 @@ import re import sys import typing import argparse -from typing import Dict, Iterator, List, Tuple +from typing import Dict, Iterator, List, Tuple, FrozenSet from . import c_parsing_helper from . import typing_util @@ -37,7 +37,7 @@ class FieldsInfo(typing.NamedTuple): special: Dict[str, List[str]] # The script isn't capable to identify named structures (ex: dtls_srtp_info) # so we keep an explicit list of them. - named_structures: List[str] + named_structures: FrozenSet[str] class CField(): # pylint: disable=too-few-public-methods From 959d9e89666045aa5769fab2299fad226c1066dc Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 09:38:15 +0200 Subject: [PATCH 24/36] scripts: ssl_session_reset_check: simplify look-up in special fields dictionary Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index 7e8c98303..f8db2b598 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -132,8 +132,7 @@ class CStruct: """Return structure field type based on either its name or the fact that it belongs to the list of special symbols/named structures""" # Check for fields with custom check rules - if (name in self.fields_info.special) and \ - (self.fields_info.special[name] == ResetBehavior.SPECIAL): + if self.fields_info.special.get(name, None) == ResetBehavior.SPECIAL: return ElementType.SPECIAL # Check for named structures if name in self.fields_info.named_structures: From d2bb7352d9334df8cc2c1368212100fd063e2c5e Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 09:40:11 +0200 Subject: [PATCH 25/36] scripts: ssl_session_reset_check: simplify search for arrays Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index f8db2b598..c035fda8a 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -125,7 +125,6 @@ class CStruct: _FIELD_RE = re.compile(r'\s*([^;]+);') _PRIVATE_FIELD_RE = re.compile(r'MBEDTLS_PRIVATE\((\w+)\)') _BARE_FIELD_RE = re.compile(r'[\t *](\w+)\Z') - _ARRAY_RE = re.compile(r'\[(\w+)\]') _NON_BLANK_RE = re.compile(r'.*\S') def _get_element_type(self, name: str, declaration: str) -> ElementType: @@ -141,8 +140,7 @@ class CStruct: if '*' in declaration: return ElementType.POINTER # Check for array - m = self._ARRAY_RE.search(declaration) - if m: + if '[' in declaration: return ElementType.ARRAY # If we get here then the field is a scalar return ElementType.SCALAR From dce56d9d2596db032911f6528f05dd34b2bb8d93 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 10:27:20 +0200 Subject: [PATCH 26/36] scripts: ssl_session_reset_check: add check function for special rules Ensure that all the fields in FieldsInfo.rules which are given a SPECIAL behavior have the corresponding entry in FieldsInfo.special, and viceversa. Signed-off-by: Valerio Setti --- .../mbedtls_framework/ssl_session_reset_check.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index c035fda8a..69f28fb31 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -163,9 +163,6 @@ class CStruct: behavior = ResetBehavior.RESET element_type = self._get_element_type(name, declaration) if behavior == ResetBehavior.SPECIAL: - if name not in self.fields_info.special: - raise Exception(f'Field {name} was given a SPECIAL behavior, but' - f'the custom check rule has not been defined') return CFieldSpecial(name, conditional, element_type, self.fields_info.special[name]) elif behavior == ResetBehavior.KEEP: @@ -225,10 +222,22 @@ class CStruct: raise Exception(f'Failed to parse non-empty line {num}. Content is: {line}') raise Exception(f'End of definition of struct {struct_name} not found') + def _check_special_fields(self): + """Ensure that all the entries FieldsInfo.rules that are given a SPECIAL + behavior also have the corresponding entry in FieldsInfo.special + (and viceversa)""" + specials_in_rules = [name for name in self.fields_info.rules \ + if self.fields_info.rules[name] == ResetBehavior.SPECIAL] + specials_names = [name for name in self.fields_info.special] + if sorted(specials_in_rules) != sorted(specials_names): + raise Exception('Fields with SPECIAL rules in FieldsInfo.rules do ' + 'not match with those in FieldsInfo.special') + def __init__(self, file_name: str, struct_name: str, fields_info: FieldsInfo) -> None: """Parse a structure definition in a C source file.""" self.fields_info = fields_info + self._check_special_fields() lines = c_parsing_helper.read_logical_lines(file_name) self.fields = list(self._structure_fields(lines, struct_name)) From 7b023c2fd21d84402f0dfe6b92023bb076e2c146 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 10:38:14 +0200 Subject: [PATCH 27/36] scripts: ssl_session_reset_check: return exception in case of unhandled behavior Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index 69f28fb31..7099c3191 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -171,7 +171,10 @@ class CStruct: return CFieldReallocate(name, conditional, element_type) elif behavior == ResetBehavior.IGNORE: return CFieldIgnore(name, conditional, element_type) - return CFieldReset(name, conditional, element_type) + elif behavior == ResetBehavior.RESET: + return CFieldReset(name, conditional, element_type) + else: + raise Exception(f'Unhandled behavior {behavior}') @staticmethod def _continue_parsing_preprocessor(argument: str, line: str, From 0c09851b861c3e9a68bbb58176a4ebc1bdbede17 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 10:49:14 +0200 Subject: [PATCH 28/36] scripts: ssl_session_reset_check: enforce all the fields to have an explicit reset behavior Do not assume RESET behavior by default. Each fields must have an explicit reset rule associated in FieldsInfo. Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index 7099c3191..507759466 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -156,11 +156,10 @@ class CStruct: raise Exception(f'Field name not found in "{declaration}"') name = m.group(1) conditional = ' && '.join(conditionals) - # Get the expected behavior on reset or use RESET by default. - if name in self.fields_info.rules: - behavior = self.fields_info.rules[name] - else: - behavior = ResetBehavior.RESET + # Get the expected behavior on reset + if name not in self.fields_info.rules: + raise Exception(f'Field {name} does not have an associated behavior') + behavior = self.fields_info.rules[name] element_type = self._get_element_type(name, declaration) if behavior == ResetBehavior.SPECIAL: return CFieldSpecial(name, conditional, element_type, From 627e24b10601f6235a54f0a1e703da679d385836 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 10:57:03 +0200 Subject: [PATCH 29/36] scripts: ssl_session_reset_check: fix check for named structures when field is kept Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index 507759466..bc64ecbb8 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -75,8 +75,8 @@ class CFieldKeep(CField): f'sizeof(after->{self.name}));'] elif self.element_type == ElementType.NAMED_STRUCTURE: return [f'TEST_MEMORY_COMPARE(&(before->{self.name}), ' - f'sizeof(before->{self.name}), &(after.{self.name}), ' - f'sizeof(after.{self.name}));'] + f'sizeof(before->{self.name}), &(after->{self.name}), ' + f'sizeof(after->{self.name}));'] return super().check_value() class CFieldReset(CField): From 1f94e7e30a17967c1bb8eb9f329030daf9feb512 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 11:19:01 +0200 Subject: [PATCH 30/36] scripts: ssl_session_reset_check: return exception if parsing of preprocessor directive fails If next(lines) return StopIteration while processing a preprocessor directive then that's an expected failure that should be reported to the user. Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index bc64ecbb8..f4369af4d 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -183,7 +183,8 @@ class CStruct: try: argument = argument[:-1] + ' ' + next(lines)[1] except StopIteration: - break + raise Exception(f'Unexpected end of the structure reached while ' + ' parsing a C preprocessor directive') return argument def _structure_fields(self, From d3f1a0332692481560b17771afb9ff55263afc2a Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 14:58:48 +0200 Subject: [PATCH 31/36] scripts: ssl_session_reset_check: alignment check between rules definition and C struct fields Ensure that: - each field defined in the C structure has a rule - each rule defined in FieldsInfo.rules has a corresponding C structure field Signed-off-by: Valerio Setti --- .../ssl_session_reset_check.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index f4369af4d..016728c14 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -236,6 +236,22 @@ class CStruct: raise Exception('Fields with SPECIAL rules in FieldsInfo.rules do ' 'not match with those in FieldsInfo.special') + def _check_rules_struct_fields_matching(self): + """Ensure that for each field of the given FieldsInfo.rules there is + an entry in the parsed C structure and viceversa""" + given_list = [name for name in self.fields_info.rules] + parsed_list = [field.name for field in self.fields] + given_not_parsed = [x for x in given_list if x not in parsed_list] + parsed_not_given = [x for x in parsed_list if x not in given_list] + if len(parsed_not_given) > 0: + raise Exception(f'Following fields are defined in the C structure, ' + f'but are not given a reset behavior rule: ' + f'{parsed_not_given}') + if len(given_not_parsed) > 0: + raise Exception(f'Following fields are given a reset behavior rule, ' + f'but are not found in the C structure: ' + f'{given_not_parsed}') + def __init__(self, file_name: str, struct_name: str, fields_info: FieldsInfo) -> None: """Parse a structure definition in a C source file.""" @@ -243,6 +259,7 @@ class CStruct: self._check_special_fields() lines = c_parsing_helper.read_logical_lines(file_name) self.fields = list(self._structure_fields(lines, struct_name)) + self._check_rules_struct_fields_matching() class SSLContextStruct(CStruct): From 0f8b282730dd76d1f54e12f42bbcd894ed1a00ae Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 15:05:13 +0200 Subject: [PATCH 32/36] scripts: ssl_session_reset_check: pass out only to write_check_function Do not pass it to SSLContextStruct init function and don't store it in that class. Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index 016728c14..54c5612a5 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -266,15 +266,12 @@ class SSLContextStruct(CStruct): # pylint: disable=too-few-public-methods """Information about the fields of struct mbedtls_ssl_context.""" - def __init__(self, out: typing_util.Writable, - fields_info: FieldsInfo) -> None: - self.out = out + def __init__(self, fields_info: FieldsInfo) -> None: super().__init__('include/mbedtls/ssl.h', 'mbedtls_ssl_context', fields_info) - def write_check_function(self) -> None: + def write_check_function(self, out: typing_util.Writable) -> None: """Write the generated context-checking function to the output.""" - out = self.out out.write(f"""\ /* * Copyright The Mbed TLS Contributors @@ -341,5 +338,5 @@ def main(fields_info: FieldsInfo): output_file = parsed_args.output_file with open(output_file, 'wt') as out: - ssl_context = SSLContextStruct(out, fields_info) - ssl_context.write_check_function() + ssl_context = SSLContextStruct(fields_info) + ssl_context.write_check_function(out) From fb70ecde4e10ac5e64cbef3f79aa37e0f465499f Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 15:17:21 +0200 Subject: [PATCH 33/36] scripts: ssl_session_reset_check: fix check for special fields in _get_element_type The previous check was completely wrong since it was comparing objects of different types. The only things that need to be checked here is that 'name' appears in the FieldsInfo.special dictionary. Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index 54c5612a5..a72769f5d 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -131,7 +131,7 @@ class CStruct: """Return structure field type based on either its name or the fact that it belongs to the list of special symbols/named structures""" # Check for fields with custom check rules - if self.fields_info.special.get(name, None) == ResetBehavior.SPECIAL: + if name in self.fields_info.special: return ElementType.SPECIAL # Check for named structures if name in self.fields_info.named_structures: From 81f943b4559987de46edebd1e44016a8cf0cf5e4 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 15:44:32 +0200 Subject: [PATCH 34/36] scripts: ssl_session_reset_check: fix preprocessor directive parsing Now all various formats are supported. Tested on the 'conf' field with the following types of guards: struct mbedtls_ssl_context { #if (defined(AAA) && \ defined(BBB)) #if (defined(CCC) && \ defined(DDD)) #if defined(EEE) && \ defined(FFF) #if defined(GGG) && defined(HHH) \ defined(III) const mbedtls_ssl_config *MBEDTLS_PRIVATE(conf); /*!< configuration information */ #endif #endif #endif #endif Signed-off-by: Valerio Setti --- .../ssl_session_reset_check.py | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index a72769f5d..eec69410d 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -43,12 +43,12 @@ class CField(): # pylint: disable=too-few-public-methods """Information about one field of a C struct.""" name: str - conditional: str + conditional: List[str] element_type: ElementType - def __init__(self, name: str, conditional: str, element_type: ElementType): + def __init__(self, name: str, conditionals: List[str], element_type: ElementType): self.name = name - self.conditional = conditional + self.conditionals = conditionals.copy() self.element_type = element_type def check_value(self) -> List[str]: @@ -155,37 +155,39 @@ class CStruct: if not m: raise Exception(f'Field name not found in "{declaration}"') name = m.group(1) - conditional = ' && '.join(conditionals) # Get the expected behavior on reset if name not in self.fields_info.rules: raise Exception(f'Field {name} does not have an associated behavior') behavior = self.fields_info.rules[name] element_type = self._get_element_type(name, declaration) if behavior == ResetBehavior.SPECIAL: - return CFieldSpecial(name, conditional, element_type, + return CFieldSpecial(name, conditionals, element_type, self.fields_info.special[name]) elif behavior == ResetBehavior.KEEP: - return CFieldKeep(name, conditional, element_type) + return CFieldKeep(name, conditionals, element_type) elif behavior == ResetBehavior.REALLOCATE: - return CFieldReallocate(name, conditional, element_type) + return CFieldReallocate(name, conditionals, element_type) elif behavior == ResetBehavior.IGNORE: - return CFieldIgnore(name, conditional, element_type) + return CFieldIgnore(name, conditionals, element_type) elif behavior == ResetBehavior.RESET: - return CFieldReset(name, conditional, element_type) + return CFieldReset(name, conditionals, element_type) else: raise Exception(f'Unhandled behavior {behavior}') @staticmethod - def _continue_parsing_preprocessor(argument: str, line: str, + def _continue_parsing_preprocessor(arguments: str, lines: Iterator[Tuple[int, str]]) -> str: """Append continuation lines of preprocesssor directive.""" - while line.endswith('\\'): + while True: try: - argument = argument[:-1] + ' ' + next(lines)[1] + line_content = next(lines)[1] + arguments = arguments + '\n' + line_content except StopIteration: - raise Exception(f'Unexpected end of the structure reached while ' + raise Exception('Unexpected end of file reached while ' ' parsing a C preprocessor directive') - return argument + if not line_content.endswith('\\'): + break + return arguments def _structure_fields(self, lines: Iterator[Tuple[int, str]], @@ -205,12 +207,22 @@ class CStruct: return m = self._PREPROCESSOR_RE.match(line) if m: - argument = m.group(2) + # If the preprocessor directives are included in parentheses (ex: + # "(defined(AAA) && defined(BBB))") then 'line' contains the full directive + # including new lines (if present) so we can just copy that. + # If instead outer parentheses are missing, ex: "defined(AAA) && defined(BBB)" + # then parsing stops at the end of the line so we need to keep parsing + # manually if there is a "\" at the end of the line. if line.endswith('\\'): - argument = self._continue_parsing_preprocessor(argument, line, lines) + arguments = m.group(2) + arguments = self._continue_parsing_preprocessor(arguments, lines) + one_conditional = '#if ' + arguments + else: + one_conditional = line + one_conditional = one_conditional + '\n' directive = m.group(1) if directive == 'if': - conditionals.append('(' + argument + ')') + conditionals.append(one_conditional) elif directive == 'endif': del conditionals[-1] else: @@ -306,12 +318,12 @@ int mbedtls_test_ssl_check_context_after_session_reset(const mbedtls_ssl_context /* *INDENT-OFF* */ """) for field in self.fields: - if field.conditional: - out.write(f'#if {field.conditional}\n') + if len(field.conditionals) > 0: + out.write('\n'.join(field.conditionals)) for check_line in field.check_value(): out.write(f' {check_line}\n') - if field.conditional: - out.write('#endif\n') + if len(field.conditionals) > 0: + out.write('#endif\n' * len(field.conditionals)) out.write(f"""\ /* *INDENT-ON* */ From 808f894cbffeb1772de72bad0c118cfcdeb847d0 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 23:07:59 +0200 Subject: [PATCH 35/36] scripts: ssl_session_reset_check: resolve pylint issues Signed-off-by: Valerio Setti --- scripts/mbedtls_framework/ssl_session_reset_check.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index eec69410d..1c6bac968 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -65,9 +65,9 @@ class CFieldKeep(CField): # pylint: disable=too-few-public-methods """Field kept unchanged.""" def check_value(self) -> List[str]: - if (self.element_type == ElementType.SCALAR): + if self.element_type == ElementType.SCALAR: return [f'TEST_EQUAL(before->{self.name}, after->{self.name});'] - elif (self.element_type == ElementType.POINTER): + elif self.element_type == ElementType.POINTER: return [f'TEST_ASSERT(before->{self.name} == after->{self.name});'] elif self.element_type == ElementType.ARRAY: return [f'TEST_MEMORY_COMPARE(before->{self.name}, ' @@ -108,7 +108,7 @@ class CFieldSpecial(CField): """Field with a custom check.""" custom_behavior: List[str] - def __init__(self, name: str, conditional: str, element_type: ElementType, + def __init__(self, name: str, conditional: List[str], element_type: ElementType, custom_behavior: List[str]): self.custom_behavior = custom_behavior super().__init__(name, conditional, element_type) @@ -193,6 +193,7 @@ class CStruct: lines: Iterator[Tuple[int, str]], struct_name: str) -> Iterator[CField]: """Yield a CField object for each field of the given structure.""" + # pylint: disable=too-many-branches found_start = False for num, line in lines: m = self._STRUCT_RE.match(line) @@ -243,7 +244,7 @@ class CStruct: (and viceversa)""" specials_in_rules = [name for name in self.fields_info.rules \ if self.fields_info.rules[name] == ResetBehavior.SPECIAL] - specials_names = [name for name in self.fields_info.special] + specials_names = list(self.fields_info.special) if sorted(specials_in_rules) != sorted(specials_names): raise Exception('Fields with SPECIAL rules in FieldsInfo.rules do ' 'not match with those in FieldsInfo.special') @@ -251,7 +252,7 @@ class CStruct: def _check_rules_struct_fields_matching(self): """Ensure that for each field of the given FieldsInfo.rules there is an entry in the parsed C structure and viceversa""" - given_list = [name for name in self.fields_info.rules] + given_list = list(self.fields_info.rules) parsed_list = [field.name for field in self.fields] given_not_parsed = [x for x in given_list if x not in parsed_list] parsed_not_given = [x for x in parsed_list if x not in given_list] From 5e6adaf3a4732ba4689a25440226a63adfde4738 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Tue, 23 Jun 2026 23:23:18 +0200 Subject: [PATCH 36/36] scripts: ssl_session_reset_check: use sets in _check_special_fields and _check_rules_struct_fields_matching Signed-off-by: Valerio Setti --- .../ssl_session_reset_check.py | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/scripts/mbedtls_framework/ssl_session_reset_check.py b/scripts/mbedtls_framework/ssl_session_reset_check.py index 1c6bac968..0cc50be36 100644 --- a/scripts/mbedtls_framework/ssl_session_reset_check.py +++ b/scripts/mbedtls_framework/ssl_session_reset_check.py @@ -242,28 +242,25 @@ class CStruct: """Ensure that all the entries FieldsInfo.rules that are given a SPECIAL behavior also have the corresponding entry in FieldsInfo.special (and viceversa)""" - specials_in_rules = [name for name in self.fields_info.rules \ - if self.fields_info.rules[name] == ResetBehavior.SPECIAL] - specials_names = list(self.fields_info.special) - if sorted(specials_in_rules) != sorted(specials_names): - raise Exception('Fields with SPECIAL rules in FieldsInfo.rules do ' - 'not match with those in FieldsInfo.special') + in_rules = frozenset(name for name in self.fields_info.rules + if self.fields_info.rules[name] == ResetBehavior.SPECIAL) + in_special = frozenset(self.fields_info.special) + if in_rules != in_special: + raise Exception(f'Fields with SPECIAL rule in FieldsInfo.rules but ' + f'not listed in FieldsInfo.special: {in_rules - in_special}. ' + f'Fields listed FieldsInfo.special, but not given a' + f'SPECIAL rule in FieldsInfo.rules: {in_special - in_rules}.') def _check_rules_struct_fields_matching(self): """Ensure that for each field of the given FieldsInfo.rules there is an entry in the parsed C structure and viceversa""" - given_list = list(self.fields_info.rules) - parsed_list = [field.name for field in self.fields] - given_not_parsed = [x for x in given_list if x not in parsed_list] - parsed_not_given = [x for x in parsed_list if x not in given_list] - if len(parsed_not_given) > 0: - raise Exception(f'Following fields are defined in the C structure, ' - f'but are not given a reset behavior rule: ' - f'{parsed_not_given}') - if len(given_not_parsed) > 0: - raise Exception(f'Following fields are given a reset behavior rule, ' - f'but are not found in the C structure: ' - f'{given_not_parsed}') + given = frozenset(name for name in self.fields_info.rules) + parsed = frozenset(field.name for field in self.fields) + if given != parsed: + raise Exception(f'Fields found in the C struct but not given a ' + f'reset behavior: {parsed - given}. ' + f'Fields given a reset behavior but not found in ' + f'the C struct: {given - parsed}') def __init__(self, file_name: str, struct_name: str, fields_info: FieldsInfo) -> None: