From 45709705e7ff0cc1550265b7ce9016d785a58cc8 Mon Sep 17 00:00:00 2001 From: EdouardMalot Date: Thu, 25 Jun 2026 16:48:42 +0200 Subject: [PATCH 1/6] =?UTF-8?q?Fix=20TLS=201.3=20record=20padding=20strip?= =?UTF-8?q?=20in=20=5Fnx=5Fsecure=5Ftls=5Fprocess=5Frecord=20(RFC=208446?= =?UTF-8?q?=20=C2=A75.4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inner content type was read as the literal last byte of the decrypted plaintext. Per RFC 8446 §5.4 that byte is followed by an arbitrary-length zero padding, so the receiver must scan back from the end skipping zeros to find it. Without this, any client that pads its TLS 1.3 records gets a fatal `unexpected_message` alert on its first record. Java JDK HttpClient pads by default since 11 (traffic-analysis resistance), so every Java HTTPS client tripped on this. Reproducible with OpenSSL using `-record_padding N`. --- nx_secure/src/nx_secure_tls_process_record.c | 49 ++++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/nx_secure/src/nx_secure_tls_process_record.c b/nx_secure/src/nx_secure_tls_process_record.c index 6d2f7f339..b6d866bd9 100644 --- a/nx_secure/src/nx_secure_tls_process_record.c +++ b/nx_secure/src/nx_secure_tls_process_record.c @@ -276,23 +276,44 @@ NX_PACKET *decrypted_packet; if (status == NX_SECURE_TLS_SUCCESS) { - /* In TLS 1.3, encrypted records have a single byte at the - record that contains the message type (e.g. application data, - ect.), which is now the ACTUAL message type. */ - status = nx_packet_data_extract_offset(decrypted_packet, - decrypted_packet -> nx_packet_length - 1, - &message_type, 1, &bytes_copied); - if (status || (bytes_copied != 1)) + /* RFC 8446 §5.4: the inner content type is the last + * NON-ZERO byte of the plaintext; everything after it is + * record padding. Reading the literal last byte broke any + * padded record (Java JDK HttpClient pads by default) — + * scan back, skip the zeros. + */ { - error_status = NX_SECURE_TLS_INVALID_PACKET; + ULONG scan_offset = decrypted_packet -> nx_packet_length; + message_type = 0; + while (scan_offset > 0) + { + scan_offset--; + status = nx_packet_data_extract_offset(decrypted_packet, + scan_offset, + &message_type, 1, &bytes_copied); + if (status || (bytes_copied != 1)) + { + error_status = NX_SECURE_TLS_INVALID_PACKET; + message_type = 0; + break; + } + if (message_type != 0) + { + break; + } + } + if (message_type == 0) + { + error_status = NX_SECURE_TLS_INVALID_PACKET; + message_length = 0; + } + else + { + message_length = scan_offset; + } + decrypted_packet -> nx_packet_length = message_length; } - /* Remove the content type byte from the data length to process. */ - message_length = message_length - 1; - - /* Adjust packet length. */ - decrypted_packet -> nx_packet_length = message_length; - /* Increment the sequence number. This is done in the MAC verify step for 1.2 and earlier, but AEAD includes the MAC so we don't check the MAC and need to increment here. */ From c54255b349d9fef87079134b06d9cdde01cf8e25 Mon Sep 17 00:00:00 2001 From: EdouardMalot Date: Tue, 4 Aug 2026 12:18:52 +0200 Subject: [PATCH 2/6] Move scan_offset to top-of-function locals, drop scoped block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow the existing style of nx_secure_tls_process_record.c: all locals declared alongside message_length and bytes_copied at the top of the function, no nested scope just to introduce one variable. Trim the TLS 1.3 de-padding comment to say what the code does now — the history of why the previous version was wrong lives in the previous commit message and PR description. --- nx_secure/src/nx_secure_tls_process_record.c | 54 +++++++++----------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/nx_secure/src/nx_secure_tls_process_record.c b/nx_secure/src/nx_secure_tls_process_record.c index b6d866bd9..84a56678b 100644 --- a/nx_secure/src/nx_secure_tls_process_record.c +++ b/nx_secure/src/nx_secure_tls_process_record.c @@ -86,6 +86,7 @@ UCHAR header_data[NX_SECURE_TLS_RECORD_HEADER_SIZE] = {0}; /* DTLS record h USHORT message_type; UINT message_length; ULONG bytes_copied; +ULONG scan_offset; UCHAR *packet_data = NX_NULL; ULONG record_offset = 0; ULONG record_offset_next = 0; @@ -277,42 +278,37 @@ NX_PACKET *decrypted_packet; { /* RFC 8446 §5.4: the inner content type is the last - * NON-ZERO byte of the plaintext; everything after it is - * record padding. Reading the literal last byte broke any - * padded record (Java JDK HttpClient pads by default) — - * scan back, skip the zeros. - */ + non-zero byte of the plaintext; any bytes after it are + record padding to be stripped. */ + scan_offset = decrypted_packet -> nx_packet_length; + message_type = 0; + while (scan_offset > 0) { - ULONG scan_offset = decrypted_packet -> nx_packet_length; - message_type = 0; - while (scan_offset > 0) - { - scan_offset--; - status = nx_packet_data_extract_offset(decrypted_packet, - scan_offset, - &message_type, 1, &bytes_copied); - if (status || (bytes_copied != 1)) - { - error_status = NX_SECURE_TLS_INVALID_PACKET; - message_type = 0; - break; - } - if (message_type != 0) - { - break; - } - } - if (message_type == 0) + scan_offset--; + status = nx_packet_data_extract_offset(decrypted_packet, + scan_offset, + &message_type, 1, &bytes_copied); + if (status || (bytes_copied != 1)) { error_status = NX_SECURE_TLS_INVALID_PACKET; - message_length = 0; + message_type = 0; + break; } - else + if (message_type != 0) { - message_length = scan_offset; + break; } - decrypted_packet -> nx_packet_length = message_length; } + if (message_type == 0) + { + error_status = NX_SECURE_TLS_INVALID_PACKET; + message_length = 0; + } + else + { + message_length = scan_offset; + } + decrypted_packet -> nx_packet_length = message_length; /* Increment the sequence number. This is done in the MAC verify step for 1.2 and earlier, but AEAD includes the MAC so we don't From ac7a9677a3eae0147df89af948e60f0863f39daf Mon Sep 17 00:00:00 2001 From: EdouardMalot Date: Tue, 4 Aug 2026 12:19:54 +0200 Subject: [PATCH 3/6] Return unexpected_message when the plaintext has no inner content type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RFC 8446 §5.4 requires "unexpected_message" when a decrypted record contains only padding. NX_SECURE_TLS_UNEXPECTED_MESSAGE already maps to the correct alert (10, fatal) in nx_secure_tls_map_error_to_alert.c; NX_SECURE_TLS_INVALID_PACKET fell through to internal_error, which misidentified a peer protocol violation as our own bug. The extract-failure path just above still returns INVALID_PACKET — that one really is a local packet-handling error and internal_error is the honest signal. --- nx_secure/src/nx_secure_tls_process_record.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nx_secure/src/nx_secure_tls_process_record.c b/nx_secure/src/nx_secure_tls_process_record.c index 84a56678b..229e18fd8 100644 --- a/nx_secure/src/nx_secure_tls_process_record.c +++ b/nx_secure/src/nx_secure_tls_process_record.c @@ -301,7 +301,7 @@ NX_PACKET *decrypted_packet; } if (message_type == 0) { - error_status = NX_SECURE_TLS_INVALID_PACKET; + error_status = NX_SECURE_TLS_UNEXPECTED_MESSAGE; message_length = 0; } else From eeff2d486e8f69c5ae81382974fb411e56480a9e Mon Sep 17 00:00:00 2001 From: EdouardMalot Date: Tue, 4 Aug 2026 12:47:04 +0200 Subject: [PATCH 4/6] Scan the plaintext directly instead of calling extract per byte MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nx_packet_data_extract_offset() walks the packet chain from the head on every call. Since the de-padding scan starts at the end of the record, each call traversed the whole chain to reach the last fragment before copying a single byte — one full walk per byte examined. On a maximally padded 16 KB record that is on the order of 16 000 chain walks handed to the peer, which the RFC lets it perform arbitrarily. Walk the fragments once, remember the offset of the last non-zero byte. The traversal always visits every plaintext byte so the cost no longer depends on the amount of padding, which matches the stance the neighbouring TLS 1.2 path already takes on padding-related timing channels (see the MAC-check comment a few lines below). The extract-failure branch disappears with the extract call, so the remaining error path is exactly the peer protocol violation (all-zero plaintext) reporting unexpected_message. --- nx_secure/src/nx_secure_tls_process_record.c | 53 ++++++++++++-------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/nx_secure/src/nx_secure_tls_process_record.c b/nx_secure/src/nx_secure_tls_process_record.c index 229e18fd8..70009b6b7 100644 --- a/nx_secure/src/nx_secure_tls_process_record.c +++ b/nx_secure/src/nx_secure_tls_process_record.c @@ -86,7 +86,11 @@ UCHAR header_data[NX_SECURE_TLS_RECORD_HEADER_SIZE] = {0}; /* DTLS record h USHORT message_type; UINT message_length; ULONG bytes_copied; -ULONG scan_offset; +NX_PACKET *scan_fragment; +UCHAR *scan_ptr; +ULONG running_offset; +ULONG last_nonzero_offset; +UCHAR last_nonzero_byte; UCHAR *packet_data = NX_NULL; ULONG record_offset = 0; ULONG record_offset_next = 0; @@ -279,34 +283,43 @@ NX_PACKET *decrypted_packet; /* RFC 8446 §5.4: the inner content type is the last non-zero byte of the plaintext; any bytes after it are - record padding to be stripped. */ - scan_offset = decrypted_packet -> nx_packet_length; - message_type = 0; - while (scan_offset > 0) + record padding to be stripped. Walk the packet chain + directly and always visit every byte, so the cost does + not depend on how much padding the peer added — the + neighbouring TLS 1.2 path takes the same stance for + exactly this reason (see the MAC-check comment below). */ + last_nonzero_offset = 0; + last_nonzero_byte = 0; + running_offset = 0; + scan_fragment = decrypted_packet; + while (scan_fragment != NX_NULL) { - scan_offset--; - status = nx_packet_data_extract_offset(decrypted_packet, - scan_offset, - &message_type, 1, &bytes_copied); - if (status || (bytes_copied != 1)) + for (scan_ptr = scan_fragment -> nx_packet_prepend_ptr; + scan_ptr < scan_fragment -> nx_packet_append_ptr; + scan_ptr++) { - error_status = NX_SECURE_TLS_INVALID_PACKET; - message_type = 0; - break; - } - if (message_type != 0) - { - break; + if (*scan_ptr != 0) + { + last_nonzero_byte = *scan_ptr; + last_nonzero_offset = running_offset; + } + running_offset++; } + scan_fragment = scan_fragment -> nx_packet_next; } - if (message_type == 0) + if (last_nonzero_byte == 0) { - error_status = NX_SECURE_TLS_UNEXPECTED_MESSAGE; + /* No non-zero byte in the plaintext: the peer sent a + record with no inner content type. RFC 8446 §5.4 + requires an "unexpected_message" alert. */ + error_status = NX_SECURE_TLS_UNEXPECTED_MESSAGE; + message_type = 0; message_length = 0; } else { - message_length = scan_offset; + message_type = last_nonzero_byte; + message_length = last_nonzero_offset; } decrypted_packet -> nx_packet_length = message_length; From 7dd2711976cf1deaa395d13c7c2993c2285bd1c1 Mon Sep 17 00:00:00 2001 From: EdouardMalot Date: Tue, 4 Aug 2026 13:18:44 +0200 Subject: [PATCH 5/6] Add regression tests for the TLS 1.3 record de-padding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cover the four cases that matter for the §5.4 fix: 1. Padded record — inner type recovered, length excludes both the type byte and the padding. 2. All-zero plaintext — rejected with unexpected_message. 3. Unpadded record — behaviour matches the pre-fix arithmetic (length - 1). This is the regression-risk assertion for existing traffic that never triggered the bug. 4. Chained-packet plaintext — the scan walks through NX_PACKET fragments, not just the head. Forced by using a plaintext larger than a single pool packet. Requires exposing the de-padding scan as _nx_secure_tls_1_3_strip_padding so the test can drive it directly without standing up a full TLS 1.3 AEAD session. _nx_secure_tls_process_record now delegates to the same helper, so the behaviour under test is identical to the production path. --- nx_secure/src/nx_secure_tls_process_record.c | 130 +++++++++++------- .../nx_secure_tls_process_record_test.c | 107 ++++++++++++++ 2 files changed, 191 insertions(+), 46 deletions(-) diff --git a/nx_secure/src/nx_secure_tls_process_record.c b/nx_secure/src/nx_secure_tls_process_record.c index 70009b6b7..31678991d 100644 --- a/nx_secure/src/nx_secure_tls_process_record.c +++ b/nx_secure/src/nx_secure_tls_process_record.c @@ -26,6 +26,12 @@ static VOID _nx_secure_tls_packet_trim(NX_PACKET *packet_ptr); +#if (NX_SECURE_TLS_TLS_1_3_ENABLED) +UINT _nx_secure_tls_1_3_strip_padding(NX_PACKET *decrypted_packet, + USHORT *message_type_ptr, + UINT *message_length_ptr); +#endif + /**************************************************************************/ /* */ /* FUNCTION RELEASE */ @@ -86,11 +92,6 @@ UCHAR header_data[NX_SECURE_TLS_RECORD_HEADER_SIZE] = {0}; /* DTLS record h USHORT message_type; UINT message_length; ULONG bytes_copied; -NX_PACKET *scan_fragment; -UCHAR *scan_ptr; -ULONG running_offset; -ULONG last_nonzero_offset; -UCHAR last_nonzero_byte; UCHAR *packet_data = NX_NULL; ULONG record_offset = 0; ULONG record_offset_next = 0; @@ -281,47 +282,9 @@ NX_PACKET *decrypted_packet; if (status == NX_SECURE_TLS_SUCCESS) { - /* RFC 8446 §5.4: the inner content type is the last - non-zero byte of the plaintext; any bytes after it are - record padding to be stripped. Walk the packet chain - directly and always visit every byte, so the cost does - not depend on how much padding the peer added — the - neighbouring TLS 1.2 path takes the same stance for - exactly this reason (see the MAC-check comment below). */ - last_nonzero_offset = 0; - last_nonzero_byte = 0; - running_offset = 0; - scan_fragment = decrypted_packet; - while (scan_fragment != NX_NULL) - { - for (scan_ptr = scan_fragment -> nx_packet_prepend_ptr; - scan_ptr < scan_fragment -> nx_packet_append_ptr; - scan_ptr++) - { - if (*scan_ptr != 0) - { - last_nonzero_byte = *scan_ptr; - last_nonzero_offset = running_offset; - } - running_offset++; - } - scan_fragment = scan_fragment -> nx_packet_next; - } - if (last_nonzero_byte == 0) - { - /* No non-zero byte in the plaintext: the peer sent a - record with no inner content type. RFC 8446 §5.4 - requires an "unexpected_message" alert. */ - error_status = NX_SECURE_TLS_UNEXPECTED_MESSAGE; - message_type = 0; - message_length = 0; - } - else - { - message_type = last_nonzero_byte; - message_length = last_nonzero_offset; - } - decrypted_packet -> nx_packet_length = message_length; + error_status = _nx_secure_tls_1_3_strip_padding(decrypted_packet, + &message_type, + &message_length); /* Increment the sequence number. This is done in the MAC verify step for 1.2 and earlier, but AEAD includes the MAC so we don't @@ -676,3 +639,78 @@ NX_PACKET *current_ptr; message_length -= (ULONG)(current_ptr -> nx_packet_append_ptr - current_ptr -> nx_packet_prepend_ptr); } } + + +#if (NX_SECURE_TLS_TLS_1_3_ENABLED) +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _nx_secure_tls_1_3_strip_padding PORTABLE C */ +/* */ +/* AUTHOR */ +/* */ +/* Edouard MALOT */ +/* */ +/* DESCRIPTION */ +/* */ +/* Recover the inner content type from a decrypted TLS 1.3 record and */ +/* strip the trailing zero-byte padding. Per RFC 8446 §5.4, the inner */ +/* content type is the last non-zero byte of the plaintext; all bytes */ +/* after it are padding. The scan visits every plaintext byte, so its */ +/* cost is independent of how much padding the peer added. */ +/* */ +/* INPUT */ +/* */ +/* decrypted_packet Decrypted record (possibly */ +/* spanning chained fragments) */ +/* message_type_ptr Set to the inner content type */ +/* message_length_ptr Set to the length excluding */ +/* the type byte and padding */ +/* */ +/* OUTPUT */ +/* */ +/* NX_SECURE_TLS_SUCCESS Inner type recovered */ +/* NX_SECURE_TLS_UNEXPECTED_MESSAGE Plaintext had no non-zero */ +/* byte (§5.4 violation) */ +/* */ +/**************************************************************************/ +UINT _nx_secure_tls_1_3_strip_padding(NX_PACKET *decrypted_packet, + USHORT *message_type_ptr, + UINT *message_length_ptr) +{ +NX_PACKET *scan_fragment; +UCHAR *scan_ptr; +ULONG running_offset = 0; +ULONG last_nonzero_offset = 0; +UCHAR last_nonzero_byte = 0; + + for (scan_fragment = decrypted_packet; scan_fragment != NX_NULL; scan_fragment = scan_fragment -> nx_packet_next) + { + for (scan_ptr = scan_fragment -> nx_packet_prepend_ptr; + scan_ptr < scan_fragment -> nx_packet_append_ptr; + scan_ptr++) + { + if (*scan_ptr != 0) + { + last_nonzero_byte = *scan_ptr; + last_nonzero_offset = running_offset; + } + running_offset++; + } + } + + if (last_nonzero_byte == 0) + { + *message_type_ptr = 0; + *message_length_ptr = 0; + decrypted_packet -> nx_packet_length = 0; + return(NX_SECURE_TLS_UNEXPECTED_MESSAGE); + } + + *message_type_ptr = (USHORT)last_nonzero_byte; + *message_length_ptr = last_nonzero_offset; + decrypted_packet -> nx_packet_length = last_nonzero_offset; + return(NX_SECURE_TLS_SUCCESS); +} +#endif /* NX_SECURE_TLS_TLS_1_3_ENABLED */ diff --git a/test/regression/nx_secure_test/nx_secure_tls_process_record_test.c b/test/regression/nx_secure_test/nx_secure_tls_process_record_test.c index 5ac49ef0c..62b2b9a5c 100644 --- a/test/regression/nx_secure_test/nx_secure_tls_process_record_test.c +++ b/test/regression/nx_secure_test/nx_secure_tls_process_record_test.c @@ -20,6 +20,12 @@ #if !defined(NX_SECURE_TLS_CLIENT_DISABLED) && !defined(NX_SECURE_TLS_SERVER_DISABLED) extern VOID test_control_return(UINT status); +#if (NX_SECURE_TLS_TLS_1_3_ENABLED) +extern UINT _nx_secure_tls_1_3_strip_padding(NX_PACKET *decrypted_packet, + USHORT *message_type_ptr, + UINT *message_length_ptr); +#endif + #define METADATA_SIZE 16000 #define NUM_PACKETS 24 #define PACKET_SIZE 1536 @@ -229,6 +235,107 @@ UCHAR packet_buffer[100]; status = _nx_secure_tls_process_record(&tls_session, packet, &bytes_processed, 0); EXPECT_EQ(NX_SECURE_TLS_INVALID_PACKET, status); +#if (NX_SECURE_TLS_TLS_1_3_ENABLED) + /* Regression tests for the TLS 1.3 record de-padding (RFC 8446 §5.4). + Before the fix, the code read the literal last byte of the plaintext + as the inner content type; any padded record from a compliant peer + (JDK 11+, OpenSSL with padding on) was mishandled. */ + { + NX_PACKET *decrypted; + USHORT out_type; + UINT out_length; + UCHAR inner_plaintext[16]; + + /* Case 1: padded record. Plaintext = "hello" + type byte + 5 zeros. + The inner type must come back and the length must exclude both + the type byte and the padding. */ + tls_session.nx_secure_record_queue_header = NX_NULL; + status = nx_packet_allocate(&pool_0, &decrypted, NX_IPv4_TCP_PACKET, NX_WAIT_FOREVER); + EXPECT_EQ(NX_SUCCESS, status); + + memset(inner_plaintext, 0, sizeof(inner_plaintext)); + memcpy(inner_plaintext, "hello", 5); + inner_plaintext[5] = NX_SECURE_TLS_APPLICATION_DATA; + status = nx_packet_data_append(decrypted, inner_plaintext, 11, &pool_0, NX_WAIT_FOREVER); + EXPECT_EQ(NX_SUCCESS, status); + + out_type = 0; + out_length = 0; + status = _nx_secure_tls_1_3_strip_padding(decrypted, &out_type, &out_length); + EXPECT_EQ(NX_SECURE_TLS_SUCCESS, status); + EXPECT_EQ((USHORT)NX_SECURE_TLS_APPLICATION_DATA, out_type); + EXPECT_EQ((UINT)5, out_length); + EXPECT_EQ((ULONG)5, decrypted -> nx_packet_length); + + status = nx_packet_release(decrypted); + EXPECT_EQ(NX_SUCCESS, status); + + /* Case 2: all-zero plaintext. §5.4 says this is a peer protocol + violation and must yield unexpected_message. */ + status = nx_packet_allocate(&pool_0, &decrypted, NX_IPv4_TCP_PACKET, NX_WAIT_FOREVER); + EXPECT_EQ(NX_SUCCESS, status); + + memset(inner_plaintext, 0, sizeof(inner_plaintext)); + status = nx_packet_data_append(decrypted, inner_plaintext, 10, &pool_0, NX_WAIT_FOREVER); + EXPECT_EQ(NX_SUCCESS, status); + + out_type = 0xff; + out_length = 42; + status = _nx_secure_tls_1_3_strip_padding(decrypted, &out_type, &out_length); + EXPECT_EQ(NX_SECURE_TLS_UNEXPECTED_MESSAGE, status); + EXPECT_EQ((USHORT)0, out_type); + EXPECT_EQ((UINT)0, out_length); + + status = nx_packet_release(decrypted); + EXPECT_EQ(NX_SUCCESS, status); + + /* Case 3: unpadded record. Pre-fix arithmetic gave length - 1; the + new scan must return the exact same values so existing traffic is + unaffected. This is the regression-risk assertion. */ + status = nx_packet_allocate(&pool_0, &decrypted, NX_IPv4_TCP_PACKET, NX_WAIT_FOREVER); + EXPECT_EQ(NX_SUCCESS, status); + + memcpy(inner_plaintext, "hello", 5); + inner_plaintext[5] = NX_SECURE_TLS_APPLICATION_DATA; + status = nx_packet_data_append(decrypted, inner_plaintext, 6, &pool_0, NX_WAIT_FOREVER); + EXPECT_EQ(NX_SUCCESS, status); + + out_type = 0; + out_length = 0; + status = _nx_secure_tls_1_3_strip_padding(decrypted, &out_type, &out_length); + EXPECT_EQ(NX_SECURE_TLS_SUCCESS, status); + EXPECT_EQ((USHORT)NX_SECURE_TLS_APPLICATION_DATA, out_type); + EXPECT_EQ((UINT)5, out_length); + + status = nx_packet_release(decrypted); + EXPECT_EQ(NX_SUCCESS, status); + + /* Case 4: padded record that straddles a fragment boundary. The scan + must walk through the chained NX_PACKETs, not just the head. */ + status = nx_packet_allocate(&pool_0, &decrypted, NX_IPv4_TCP_PACKET, NX_WAIT_FOREVER); + EXPECT_EQ(NX_SUCCESS, status); + + /* Payload > single-packet capacity (1536) forces chaining. Fill with + a non-zero content byte, then the type, then trailing zeros — the + trailing zeros will land in a later fragment. */ + memset(data_buffer, 0xAB, 2000); + data_buffer[1999] = NX_SECURE_TLS_APPLICATION_DATA; + memset(&data_buffer[2000], 0, 500); + status = nx_packet_data_append(decrypted, data_buffer, 2500, &pool_0, NX_WAIT_FOREVER); + EXPECT_EQ(NX_SUCCESS, status); + + out_type = 0; + out_length = 0; + status = _nx_secure_tls_1_3_strip_padding(decrypted, &out_type, &out_length); + EXPECT_EQ(NX_SECURE_TLS_SUCCESS, status); + EXPECT_EQ((USHORT)NX_SECURE_TLS_APPLICATION_DATA, out_type); + EXPECT_EQ((UINT)1999, out_length); + + status = nx_packet_release(decrypted); + EXPECT_EQ(NX_SUCCESS, status); + } +#endif /* NX_SECURE_TLS_TLS_1_3_ENABLED */ + printf("SUCCESS!\n"); test_control_return(0); } From 485adacb8f9a328ce39a8c173250bfc4a8aeca8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 13 Aug 2026 08:46:19 -0400 Subject: [PATCH 6/6] Declared the TLS 1.3 de-padding helper in the header, as its siblings are _nx_secure_tls_1_3_strip_padding had external linkage but no declaration in any header: it was forward declared in nx_secure_tls_process_record.c and declared again with extern in the test that calls it. That is the one shape this tree does not use. nx_secure_tls.h already carries 135 internal _nx_secure_* prototypes, including a run of _nx_secure_tls_1_3_* functions, while the other internal function in this same source file, _nx_secure_tls_packet_trim, is static because nothing outside needs it. So an internal function is either declared in the header or static, and this one is used from outside its translation unit. Declare it in nx_secure_tls.h beside _nx_secure_tls_1_3_crypto_init, and drop both duplicate declarations. No behaviour change, and it removes a MISRA 8.7 deviation that would otherwise have to be documented rather than avoided. Verified by rebuilding nx_secure_tls_process_record_test against the header instead of the extern: it links and all four de-padding cases pass. Assisted-by: Claude Code (Opus 5) --- nx_secure/inc/nx_secure_tls.h | 2 ++ nx_secure/src/nx_secure_tls_process_record.c | 6 ------ .../nx_secure_test/nx_secure_tls_process_record_test.c | 6 ------ 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/nx_secure/inc/nx_secure_tls.h b/nx_secure/inc/nx_secure_tls.h index c07292867..5b21776f0 100644 --- a/nx_secure/inc/nx_secure_tls.h +++ b/nx_secure/inc/nx_secure_tls.h @@ -1407,6 +1407,8 @@ typedef struct NX_SECURE_TLS_SESSION_STRUCT #if (NX_SECURE_TLS_TLS_1_3_ENABLED) UINT _nx_secure_tls_1_3_crypto_init(NX_SECURE_TLS_SESSION *tls_session); +UINT _nx_secure_tls_1_3_strip_padding(NX_PACKET *decrypted_packet, USHORT *message_type_ptr, + UINT *message_length_ptr); UINT _nx_secure_tls_1_3_client_handshake(NX_SECURE_TLS_SESSION *tls_session, UCHAR *packet_buffer, UINT data_length, ULONG wait_option); UINT _nx_secure_tls_1_3_server_handshake(NX_SECURE_TLS_SESSION *tls_session, UCHAR *packet_buffer, diff --git a/nx_secure/src/nx_secure_tls_process_record.c b/nx_secure/src/nx_secure_tls_process_record.c index 31678991d..c3c5f918e 100644 --- a/nx_secure/src/nx_secure_tls_process_record.c +++ b/nx_secure/src/nx_secure_tls_process_record.c @@ -26,12 +26,6 @@ static VOID _nx_secure_tls_packet_trim(NX_PACKET *packet_ptr); -#if (NX_SECURE_TLS_TLS_1_3_ENABLED) -UINT _nx_secure_tls_1_3_strip_padding(NX_PACKET *decrypted_packet, - USHORT *message_type_ptr, - UINT *message_length_ptr); -#endif - /**************************************************************************/ /* */ /* FUNCTION RELEASE */ diff --git a/test/regression/nx_secure_test/nx_secure_tls_process_record_test.c b/test/regression/nx_secure_test/nx_secure_tls_process_record_test.c index 62b2b9a5c..00a8d1c27 100644 --- a/test/regression/nx_secure_test/nx_secure_tls_process_record_test.c +++ b/test/regression/nx_secure_test/nx_secure_tls_process_record_test.c @@ -20,12 +20,6 @@ #if !defined(NX_SECURE_TLS_CLIENT_DISABLED) && !defined(NX_SECURE_TLS_SERVER_DISABLED) extern VOID test_control_return(UINT status); -#if (NX_SECURE_TLS_TLS_1_3_ENABLED) -extern UINT _nx_secure_tls_1_3_strip_padding(NX_PACKET *decrypted_packet, - USHORT *message_type_ptr, - UINT *message_length_ptr); -#endif - #define METADATA_SIZE 16000 #define NUM_PACKETS 24 #define PACKET_SIZE 1536