The NEON SIMD fast path in the bundled llhttp calls
__builtin_ctzll(match_mask) without checking if match_mask is zero.
When all 16 bytes in a NEON register are valid header value characters,
match_mask is 0. Calling __builtin_ctzll(0) is undefined behavior.

GCC at -O2 exploits this by optimizing "if (match_len != 16)" to
always-true, causing HTTP 400 Bad Request for any header value longer
than 16 characters on ARM targets with NEON enabled.

Fix by explicitly checking for match_mask == 0 and setting
match_len = 16. This bug affects both aarch64 and armv7 NEON targets.

The code this patch modifies is generated, so the patch itself isn't
suitable for upstream submission, as the root cause of the error is
in the generator itself. The fix has been merged upstream[1] in
llparse 7.3.1 and is included in llhttp 9.3.1. This patch can be
dropped when nodejs updates its bundled llhttp to >= 9.3.1.

[1]: https://github.com/nodejs/llparse/pull/83

Signed-off-by: Telukula Jeevan Kumar Sahu <[email protected]>
---
 ...header-value-__builtin_ctzll-undefin.patch | 60 +++++++++++++++++++
 .../recipes-devtools/nodejs/nodejs_22.22.0.bb |  1 +
 2 files changed, 61 insertions(+)
 create mode 100644 
meta-oe/recipes-devtools/nodejs/nodejs/0001-llhttp-fix-NEON-header-value-__builtin_ctzll-undefin.patch

diff --git 
a/meta-oe/recipes-devtools/nodejs/nodejs/0001-llhttp-fix-NEON-header-value-__builtin_ctzll-undefin.patch
 
b/meta-oe/recipes-devtools/nodejs/nodejs/0001-llhttp-fix-NEON-header-value-__builtin_ctzll-undefin.patch
new file mode 100644
index 0000000000..683dddcf04
--- /dev/null
+++ 
b/meta-oe/recipes-devtools/nodejs/nodejs/0001-llhttp-fix-NEON-header-value-__builtin_ctzll-undefin.patch
@@ -0,0 +1,60 @@
+From a63a5faea54055973bf5f0a514444532563cc20d Mon Sep 17 00:00:00 2001
+From: Telukula Jeevan Kumar Sahu <[email protected]>
+Date: Fri, 27 Feb 2026 20:58:43 +0530
+Subject: [PATCH] llhttp: fix NEON header value __builtin_ctzll undefined
+ behavior
+
+When all 16 bytes match the allowed range, match_mask becomes 0 after
+the bitwise NOT. Calling __builtin_ctzll(0) is undefined behavior per
+the C standard.
+
+The code expects match_len == 16 when all bytes match (so the branch
+is skipped and p += 16 continues the loop), but this relied on
+ctzll(0) returning 64, which is not guaranteed.
+
+GCC at -O2 exploits this UB by deducing that __builtin_ctzll() result
+is always in range [0, 63], and after >> 2 always in [0, 15], which
+is never equal to 16. The compiler then optimizes
+"if (match_len != 16)" to always-true, causing every valid 16-byte
+chunk to be falsely rejected as containing an invalid character.
+
+This manifests as HTTP 400 Bad Request (HPE_INVALID_HEADER_TOKEN) for
+any HTTP header value longer than 16 characters on ARM targets with
+NEON enabled.
+
+Fix by explicitly checking for match_mask == 0 and setting
+match_len = 16, avoiding the undefined behavior entirely. This bug
+affects both aarch64 and armv7 NEON targets.
+
+The fix has been merged upstream in llparse 7.3.1 [1] and is included
+in llhttp 9.3.1. This patch can be dropped when nodejs updates its
+bundled llhttp to >= 9.3.1.
+
+[1]: https://github.com/nodejs/llparse/pull/83
+
+Upstream-Status: Inappropriate
+Signed-off-by: Telukula Jeevan Kumar Sahu <[email protected]>
+---
+ deps/llhttp/src/llhttp.c | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/deps/llhttp/src/llhttp.c b/deps/llhttp/src/llhttp.c
+index 14b731e..b0a46c6 100644
+--- a/deps/llhttp/src/llhttp.c
++++ b/deps/llhttp/src/llhttp.c
+@@ -2651,7 +2651,11 @@ static llparse_state_t llhttp__internal__run(
+         mask = vorrq_u8(mask, single);
+         narrow = vshrn_n_u16(vreinterpretq_u16_u8(mask), 4);
+         match_mask = ~vget_lane_u64(vreinterpret_u64_u8(narrow), 0);
+-        match_len = __builtin_ctzll(match_mask) >> 2;
++        if (match_mask == 0) {
++          match_len = 16;
++        } else {
++          match_len = __builtin_ctzll(match_mask) >> 2;
++        }
+         if (match_len != 16) {
+           p += match_len;
+           goto s_n_llhttp__internal__n_header_value_otherwise;
+-- 
+2.34.1
+
diff --git a/meta-oe/recipes-devtools/nodejs/nodejs_22.22.0.bb 
b/meta-oe/recipes-devtools/nodejs/nodejs_22.22.0.bb
index 05fa608047..d08c5d8318 100644
--- a/meta-oe/recipes-devtools/nodejs/nodejs_22.22.0.bb
+++ b/meta-oe/recipes-devtools/nodejs/nodejs_22.22.0.bb
@@ -33,6 +33,7 @@ SRC_URI = "https://nodejs.org/dist/v${PV}/node-v${PV}.tar.xz \
            file://0001-build-remove-redundant-mXX-flags-for-V8.patch \
            file://0001-fix-arm-Neon-intrinsics-types.patch \
            file://0001-detect-aarch64-Neon-correctly.patch \
+           
file://0001-llhttp-fix-NEON-header-value-__builtin_ctzll-undefin.patch \
            file://run-ptest \
            "
 SRC_URI:append:class-target = " \
-- 
2.34.1

-=-=-=-=-=-=-=-=-=-=-=-
Links: You receive all messages sent to this group.
View/Reply Online (#124806): 
https://lists.openembedded.org/g/openembedded-devel/message/124806
Mute This Topic: https://lists.openembedded.org/mt/118094533/21656
Group Owner: [email protected]
Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub 
[[email protected]]
-=-=-=-=-=-=-=-=-=-=-=-

Reply via email to