diff mbox series

[meta-oe] nodejs: fix NEON llhttp ctzll undefined behavior

Message ID 20260302144634.835048-1-j-sahu@ti.com
State New
Headers show
Series [meta-oe] nodejs: fix NEON llhttp ctzll undefined behavior | expand

Commit Message

Jeevan March 2, 2026, 2:46 p.m. UTC
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 <j-sahu@ti.com>
---
 ...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

Comments

Khem Raj March 2, 2026, 2:56 p.m. UTC | #1
On Mon, Mar 2, 2026 at 6:47 AM Jeevan via lists.openembedded.org <j-sahu=
ti.com@lists.openembedded.org> wrote:

> 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 <j-sahu@ti.com>
> ---
>  ...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 <j-sahu@ti.com>
> +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
>

If this fix is merged upstream then status should be 'Backport' with
appropriate links to
point to upstream fix.


> +Signed-off-by: Telukula Jeevan Kumar Sahu <j-sahu@ti.com>
> +---
> + 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/1997914
> Group Owner: openembedded-devel+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [
> raj.khem@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
>
Jeevan March 2, 2026, 3:15 p.m. UTC | #2
On Mon, Mar 2, 2026 at 08:27 PM, Khem Raj wrote:

> 
> 
> 
> On Mon, Mar 2, 2026 at 6:47 AM Jeevan via lists.openembedded.org (
> http://lists.openembedded.org ) <j-sahu= ti.com@lists.openembedded.org >
> wrote:
> 
>> 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 < j-sahu@ti.com >
>> ---
>> ...header-value-__builtin_ctzll-undefin.patch | 60 +++++++++++++++++++
>> .../recipes-devtools/nodejs/ nodejs_22.22.0.bb ( http://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 < j-sahu@ti.com >
>> +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
> 
> 
> If this fix is merged upstream then status should be 'Backport' with
> appropriate links to
> point to upstream fix.
> 
> 
> 

This patch manually applies the logic to the generated C code. Upstream (llparse/llhttp/nodejs)
would never accept patches to generated files - they'd regenerate or update the bundled version.
That's why I used "Inappropriate" per Yocto guidelines.

> 
> 
> 
>> +Signed-off-by: Telukula Jeevan Kumar Sahu < j-sahu@ti.com >
>> +---
>> + 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 (
>> http://nodejs_22.22.0.bb ) b/meta-oe/recipes-devtools/nodejs/ nodejs_22.22.0.bb
>> ( http://nodejs_22.22.0.bb )
>> index 05fa608047..d08c5d8318 100644
>> --- a/meta-oe/recipes-devtools/nodejs/ nodejs_22.22.0.bb (
>> http://nodejs_22.22.0.bb )
>> +++ b/meta-oe/recipes-devtools/nodejs/ nodejs_22.22.0.bb (
>> http://nodejs_22.22.0.bb )
>> @@ -33,6 +33,7 @@ SRC_URI = " https://nodejs.org/dist/v${PV}/node-v${PV}.tar.xz
>> ( https://nodejs.org/dist/v$%7BPV%7D/node-v$%7BPV%7D.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
>> 
>> 
>> 
>> 
> 
> 
>
Khem Raj March 2, 2026, 3:27 p.m. UTC | #3
On Mon, Mar 2, 2026 at 7:15 AM Jeevan via lists.openembedded.org <j-sahu=
ti.com@lists.openembedded.org> wrote:

> On Mon, Mar 2, 2026 at 08:27 PM, Khem Raj wrote:
>
>
>
> On Mon, Mar 2, 2026 at 6:47 AM Jeevan via lists.openembedded.org <j-sahu=
> ti.com@lists.openembedded.org> wrote:
>
>> 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 <j-sahu@ti.com>
>> ---
>>  ...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 <j-sahu@ti.com>
>> +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
>
>
> If this fix is merged upstream then status should be 'Backport' with
> appropriate links to
> point to upstream fix.
>
>
>
>   This patch manually applies the logic to the generated C code. Upstream
> (llparse/llhttp/nodejs)
>   would never accept patches to generated files - they'd regenerate or
> update the bundled version.
>   That's why I used "Inappropriate" per Yocto guidelines.
>

I see, we do not regenerate the file ? if that something lacking in the
build process in OE for this package then

>
>
>
>> +Signed-off-by: Telukula Jeevan Kumar Sahu <j-sahu@ti.com>
>> +---
>> + 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 (#124813):
> https://lists.openembedded.org/g/openembedded-devel/message/124813
> Mute This Topic: https://lists.openembedded.org/mt/118094533/1997914
> Group Owner: openembedded-devel+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [
> raj.khem@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
>
Jeevan March 2, 2026, 3:55 p.m. UTC | #4
On Mon, Mar 2, 2026 at 08:58 PM, Khem Raj wrote:

> 
> 
> 
> On Mon, Mar 2, 2026 at 7:15 AM Jeevan via lists.openembedded.org (
> http://lists.openembedded.org ) <j-sahu= ti.com@lists.openembedded.org >
> wrote:
> 
>> On Mon, Mar 2, 2026 at 08:27 PM, Khem Raj wrote:
>> 
>>> 
>>> 
>>> On Mon, Mar 2, 2026 at 6:47 AM Jeevan via lists.openembedded.org (
>>> http://lists.openembedded.org ) <j-sahu= ti.com@lists.openembedded.org >
>>> wrote:
>>> 
>>>> 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 < j-sahu@ti.com >
>>>> ---
>>>> ...header-value-__builtin_ctzll-undefin.patch | 60 +++++++++++++++++++
>>>> .../recipes-devtools/nodejs/ nodejs_22.22.0.bb ( http://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 < j-sahu@ti.com >
>>>> +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
>>> 
>>> 
>>> If this fix is merged upstream then status should be 'Backport' with
>>> appropriate links to
>>> point to upstream fix.
>>> 
>>> 
>>> 
>> 
>> This patch manually applies the logic to the generated C code. Upstream
>> (llparse/llhttp/nodejs)
>> would never accept patches to generated files - they'd regenerate or
>> update the bundled version.
>> That's why I used "Inappropriate" per Yocto guidelines.
>> 
> 
> 
> I see, we do not regenerate the file ? if that something lacking in the
> build process in OE for this package then
> 
> 

llparse is a Node.js tool that generates the HTTP parser. Node.js upstream ships this pre-generated (common for complex generators),
and most distros (Debian, Fedora, Arch) use it as-is. The nodejs recipe in meta-oe follows the same approach - I believe this is a
deliberate trade-off rather than a gap.

> 
> 
>> 
>> 
>>> 
>>> 
>>>> +Signed-off-by: Telukula Jeevan Kumar Sahu < j-sahu@ti.com >
>>>> +---
>>>> + 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 (
>>>> http://nodejs_22.22.0.bb ) b/meta-oe/recipes-devtools/nodejs/ nodejs_22.22.0.bb
>>>> ( http://nodejs_22.22.0.bb )
>>>> index 05fa608047..d08c5d8318 100644
>>>> --- a/meta-oe/recipes-devtools/nodejs/ nodejs_22.22.0.bb (
>>>> http://nodejs_22.22.0.bb )
>>>> +++ b/meta-oe/recipes-devtools/nodejs/ nodejs_22.22.0.bb (
>>>> http://nodejs_22.22.0.bb )
>>>> @@ -33,6 +33,7 @@ SRC_URI = " https://nodejs.org/dist/v${PV}/node-v${PV}.tar.xz
>>>> ( https://nodejs.org/dist/v$%7BPV%7D/node-v$%7BPV%7D.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
>>>> 
>>>> 
>>>> 
>>> 
>>> 
>>> 
>> 
>> 
>> 
>> 
> 
> 
>
diff mbox series

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 <j-sahu@ti.com>
+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 <j-sahu@ti.com>
+---
+ 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 = " \