[PATCH v2 3/3] module: Add compile-time check for embedded NUL characters

Kees Cook posted 3 patches 4 months ago
[PATCH v2 3/3] module: Add compile-time check for embedded NUL characters
Posted by Kees Cook 4 months ago
Long ago, the kernel module license checks were bypassed by embedding a
NUL character in the MODULE_LICENSE() string[1]. By using a string like
"GPL\0proprietary text", the kernel would only read "GPL" due to C string
termination at the NUL byte, allowing proprietary modules to avoid kernel
tainting and access GPL-only symbols.

The MODULE_INFO() macro stores these strings in the .modinfo ELF
section, and get_next_modinfo() uses strcmp()-family functions
which stop at the first NUL. This split the embedded string into two
separate .modinfo entries, with only the first part being processed by
license_is_gpl_compatible().

Add a compile-time check using static_assert that compares the full
string length (sizeof - 1) against __builtin_strlen(), which stops at
the first NUL. If they differ, compilation fails with a clear error
message.

While this check can still be circumvented by modifying the ELF binary
post-compilation, it prevents accidental embedded NULs and forces
intentional abuse to require deliberate binary manipulation rather than
simple source-level tricks.

Build tested with test modules containing both valid and invalid license
strings. The check correctly rejects:

    MODULE_LICENSE("GPL\0proprietary")

while accepting normal declarations:

    MODULE_LICENSE("GPL")

Link: https://lwn.net/Articles/82305/ [1]
Suggested-by: Rusty Russell <rusty@rustcorp.com.au>
Signed-off-by: Kees Cook <kees@kernel.org>
---
Cc: Luis Chamberlain <mcgrof@kernel.org>
Cc: Petr Pavlu <petr.pavlu@suse.com>
Cc: Daniel Gomez <da.gomez@kernel.org>
Cc: Sami Tolvanen <samitolvanen@google.com>
Cc: <linux-modules@vger.kernel.org>
---
 include/linux/moduleparam.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/include/linux/moduleparam.h b/include/linux/moduleparam.h
index 6907aedc4f74..915f32f7d888 100644
--- a/include/linux/moduleparam.h
+++ b/include/linux/moduleparam.h
@@ -26,6 +26,9 @@
 
 /* Generic info of form tag = "info" */
 #define MODULE_INFO(tag, info)					  \
+	static_assert(						  \
+		sizeof(info) - 1 == __builtin_strlen(info),	  \
+		"MODULE_INFO(" #tag ", ...) contains embedded NUL byte"); \
 	static const char __UNIQUE_ID(modinfo)[]			  \
 		__used __section(".modinfo") __aligned(1)		  \
 		= __MODULE_INFO_PREFIX __stringify(tag) "=" info
-- 
2.34.1
Re: [PATCH v2 3/3] module: Add compile-time check for embedded NUL characters
Posted by Matthieu Baerts 1 month, 2 weeks ago
Hi Kees, Dan,

Sorry to react on an oldish patch, but I have a question about it, see
below.

On 10/10/2025 05:06, Kees Cook wrote:
> Long ago, the kernel module license checks were bypassed by embedding a
> NUL character in the MODULE_LICENSE() string[1]. By using a string like
> "GPL\0proprietary text", the kernel would only read "GPL" due to C string
> termination at the NUL byte, allowing proprietary modules to avoid kernel
> tainting and access GPL-only symbols.
> 
> The MODULE_INFO() macro stores these strings in the .modinfo ELF
> section, and get_next_modinfo() uses strcmp()-family functions
> which stop at the first NUL. This split the embedded string into two
> separate .modinfo entries, with only the first part being processed by
> license_is_gpl_compatible().
> 
> Add a compile-time check using static_assert that compares the full
> string length (sizeof - 1) against __builtin_strlen(), which stops at
> the first NUL. If they differ, compilation fails with a clear error
> message.
> 
> While this check can still be circumvented by modifying the ELF binary
> post-compilation, it prevents accidental embedded NULs and forces
> intentional abuse to require deliberate binary manipulation rather than
> simple source-level tricks.
> 
> Build tested with test modules containing both valid and invalid license
> strings. The check correctly rejects:
> 
>     MODULE_LICENSE("GPL\0proprietary")
> 
> while accepting normal declarations:
> 
>     MODULE_LICENSE("GPL")
> 
> Link: https://lwn.net/Articles/82305/ [1]
> Suggested-by: Rusty Russell <rusty@rustcorp.com.au>
> Signed-off-by: Kees Cook <kees@kernel.org>
> ---
> Cc: Luis Chamberlain <mcgrof@kernel.org>
> Cc: Petr Pavlu <petr.pavlu@suse.com>
> Cc: Daniel Gomez <da.gomez@kernel.org>
> Cc: Sami Tolvanen <samitolvanen@google.com>
> Cc: <linux-modules@vger.kernel.org>
> ---
>  include/linux/moduleparam.h | 3 +++
>  1 file changed, 3 insertions(+)
> 
> diff --git a/include/linux/moduleparam.h b/include/linux/moduleparam.h
> index 6907aedc4f74..915f32f7d888 100644
> --- a/include/linux/moduleparam.h
> +++ b/include/linux/moduleparam.h
> @@ -26,6 +26,9 @@
>  
>  /* Generic info of form tag = "info" */
>  #define MODULE_INFO(tag, info)					  \
> +	static_assert(						  \
> +		sizeof(info) - 1 == __builtin_strlen(info),	  \
> +		"MODULE_INFO(" #tag ", ...) contains embedded NUL byte"); \

When checking MPTCP code on top of Linus tree, I get this new warning
with all MPTCP KUnit tests (net/mptcp/*_test.c), e.g.

$ touch net/mptcp/crypto_test.c && make C=1 net/mptcp/crypto_test.o
  CC [M]  net/mptcp/crypto_test.o
  CHECK   net/mptcp/crypto_test.c
net/mptcp/crypto_test.c:72:1: error: bad integer constant expression
net/mptcp/crypto_test.c:72:1: error: static assertion failed: "MODULE_INFO(license, ...) contains embedded NUL byte"
net/mptcp/crypto_test.c:73:1: error: bad integer constant expression
net/mptcp/crypto_test.c:73:1: error: static assertion failed: "MODULE_INFO(description, ...) contains embedded NUL byte"


I'm using Sparse last development version with Dan's commit:

$ sparse --version
v0.6.4-73-gfbdde312

=> fbdde312 ("builtin: implement __builtin_strlen() for constants")


And the two lines causing the warnings don't have "\0":

    72  MODULE_LICENSE("GPL");
    73  MODULE_DESCRIPTION("KUnit tests for MPTCP Crypto");


Am I missing something?

Cheers,
Matt
-- 
Sponsored by the NGI0 Core fund.
Re: [PATCH v2 3/3] module: Add compile-time check for embedded NUL characters
Posted by Dan Carpenter 1 month, 2 weeks ago
On Fri, Dec 19, 2025 at 01:29:21PM +0100, Matthieu Baerts wrote:
> net/mptcp/crypto_test.c:72:1: error: bad integer constant expression
> net/mptcp/crypto_test.c:72:1: error: static assertion failed: "MODULE_INFO(license, ...) contains embedded NUL byte"
> net/mptcp/crypto_test.c:73:1: error: bad integer constant expression
> net/mptcp/crypto_test.c:73:1: error: static assertion failed: "MODULE_INFO(description, ...) contains embedded NUL byte"

There was a fix for that posted.  Let me ping them to see if anyone is
planning to send an actual patch.

https://lore.kernel.org/all/20251211175101.GA3405942@google.com/

regards
dan carpenter
Re: [PATCH v2 3/3] module: Add compile-time check for embedded NUL characters
Posted by Matthieu Baerts 1 month, 2 weeks ago
Hi Dan, Daniel

On 19/12/2025 13:44, Dan Carpenter wrote:
> On Fri, Dec 19, 2025 at 01:29:21PM +0100, Matthieu Baerts wrote:
>> net/mptcp/crypto_test.c:72:1: error: bad integer constant expression
>> net/mptcp/crypto_test.c:72:1: error: static assertion failed: "MODULE_INFO(license, ...) contains embedded NUL byte"
>> net/mptcp/crypto_test.c:73:1: error: bad integer constant expression
>> net/mptcp/crypto_test.c:73:1: error: static assertion failed: "MODULE_INFO(description, ...) contains embedded NUL byte"
> 
> There was a fix for that posted.  Let me ping them to see if anyone is
> planning to send an actual patch.
> 
> https://lore.kernel.org/all/20251211175101.GA3405942@google.com/

Thank you both for your reply! I didn't think about looking at the v1.

I confirm that Sami's patch silences the errors on my side. Thanks!

Cheers,
Matt
-- 
Sponsored by the NGI0 Core fund.
Re: [PATCH v2 3/3] module: Add compile-time check for embedded NUL characters
Posted by Chris Li 3 weeks ago
On Fri, Dec 19, 2025 at 6:59 AM Matthieu Baerts <matttbe@kernel.org> wrote:
>
> Hi Dan, Daniel
>
> On 19/12/2025 13:44, Dan Carpenter wrote:
> > On Fri, Dec 19, 2025 at 01:29:21PM +0100, Matthieu Baerts wrote:
> >> net/mptcp/crypto_test.c:72:1: error: bad integer constant expression
> >> net/mptcp/crypto_test.c:72:1: error: static assertion failed: "MODULE_INFO(license, ...) contains embedded NUL byte"
> >> net/mptcp/crypto_test.c:73:1: error: bad integer constant expression
> >> net/mptcp/crypto_test.c:73:1: error: static assertion failed: "MODULE_INFO(description, ...) contains embedded NUL byte"
> >
> > There was a fix for that posted.  Let me ping them to see if anyone is
> > planning to send an actual patch.

Should I wait for the actual patch for sparse?

> >
> > https://lore.kernel.org/all/20251211175101.GA3405942@google.com/
>
> Thank you both for your reply! I didn't think about looking at the v1.
>
> I confirm that Sami's patch silences the errors on my side. Thanks!

Thanks for the report.

Chris
Re: [PATCH v2 3/3] module: Add compile-time check for embedded NUL characters
Posted by Daniel Gomez 1 month, 2 weeks ago
On 19/12/2025 13.29, Matthieu Baerts wrote:
> $ touch net/mptcp/crypto_test.c && make C=1 net/mptcp/crypto_test.o
>   CC [M]  net/mptcp/crypto_test.o
>   CHECK   net/mptcp/crypto_test.c
> net/mptcp/crypto_test.c:72:1: error: bad integer constant expression
> net/mptcp/crypto_test.c:72:1: error: static assertion failed: "MODULE_INFO(license, ...) contains embedded NUL byte"
> net/mptcp/crypto_test.c:73:1: error: bad integer constant expression
> net/mptcp/crypto_test.c:73:1: error: static assertion failed: "MODULE_INFO(description, ...) contains embedded NUL byte"
> 

FYI, we were discussing the fix here:

https://lore.kernel.org/all/20251211175101.GA3405942@google.com/

Sami provided a fix to the thread that you can test. It'd be good to know if it
works for you too. But we still have some questions as we are not familiar with
the sparse code.

Sami, would you mind sending a patch to the sparse list and then we can ask
questions there?
Re: [PATCH v2 3/3] module: Add compile-time check for embedded NUL characters
Posted by Hans Verkuil 3 months ago
On 10/10/2025 05:06, Kees Cook wrote:
> Long ago, the kernel module license checks were bypassed by embedding a
> NUL character in the MODULE_LICENSE() string[1]. By using a string like
> "GPL\0proprietary text", the kernel would only read "GPL" due to C string
> termination at the NUL byte, allowing proprietary modules to avoid kernel
> tainting and access GPL-only symbols.
> 
> The MODULE_INFO() macro stores these strings in the .modinfo ELF
> section, and get_next_modinfo() uses strcmp()-family functions
> which stop at the first NUL. This split the embedded string into two
> separate .modinfo entries, with only the first part being processed by
> license_is_gpl_compatible().
> 
> Add a compile-time check using static_assert that compares the full
> string length (sizeof - 1) against __builtin_strlen(), which stops at
> the first NUL. If they differ, compilation fails with a clear error
> message.
> 
> While this check can still be circumvented by modifying the ELF binary
> post-compilation, it prevents accidental embedded NULs and forces
> intentional abuse to require deliberate binary manipulation rather than
> simple source-level tricks.
> 
> Build tested with test modules containing both valid and invalid license
> strings. The check correctly rejects:
> 
>     MODULE_LICENSE("GPL\0proprietary")
> 
> while accepting normal declarations:
> 
>     MODULE_LICENSE("GPL")

Who will take this series? I can take the first two media patches and
someone else can take this last patch, or I can take all, or someone
else can take all patches. The media patches already have my 'Reviewed-by'.

Any preferences?

Regards,

	Hans

> 
> Link: https://lwn.net/Articles/82305/ [1]
> Suggested-by: Rusty Russell <rusty@rustcorp.com.au>
> Signed-off-by: Kees Cook <kees@kernel.org>
> ---
> Cc: Luis Chamberlain <mcgrof@kernel.org>
> Cc: Petr Pavlu <petr.pavlu@suse.com>
> Cc: Daniel Gomez <da.gomez@kernel.org>
> Cc: Sami Tolvanen <samitolvanen@google.com>
> Cc: <linux-modules@vger.kernel.org>
> ---
>  include/linux/moduleparam.h | 3 +++
>  1 file changed, 3 insertions(+)
> 
> diff --git a/include/linux/moduleparam.h b/include/linux/moduleparam.h
> index 6907aedc4f74..915f32f7d888 100644
> --- a/include/linux/moduleparam.h
> +++ b/include/linux/moduleparam.h
> @@ -26,6 +26,9 @@
>  
>  /* Generic info of form tag = "info" */
>  #define MODULE_INFO(tag, info)					  \
> +	static_assert(						  \
> +		sizeof(info) - 1 == __builtin_strlen(info),	  \
> +		"MODULE_INFO(" #tag ", ...) contains embedded NUL byte"); \
>  	static const char __UNIQUE_ID(modinfo)[]			  \
>  		__used __section(".modinfo") __aligned(1)		  \
>  		= __MODULE_INFO_PREFIX __stringify(tag) "=" info
Re: [PATCH v2 3/3] module: Add compile-time check for embedded NUL characters
Posted by Daniel Gomez 3 months ago
On 03/11/2025 09.54, Hans Verkuil wrote:
> On 10/10/2025 05:06, Kees Cook wrote:
>> Long ago, the kernel module license checks were bypassed by embedding a
>> NUL character in the MODULE_LICENSE() string[1]. By using a string like
>> "GPL\0proprietary text", the kernel would only read "GPL" due to C string
>> termination at the NUL byte, allowing proprietary modules to avoid kernel
>> tainting and access GPL-only symbols.
>>
>> The MODULE_INFO() macro stores these strings in the .modinfo ELF
>> section, and get_next_modinfo() uses strcmp()-family functions
>> which stop at the first NUL. This split the embedded string into two
>> separate .modinfo entries, with only the first part being processed by
>> license_is_gpl_compatible().
>>
>> Add a compile-time check using static_assert that compares the full
>> string length (sizeof - 1) against __builtin_strlen(), which stops at
>> the first NUL. If they differ, compilation fails with a clear error
>> message.
>>
>> While this check can still be circumvented by modifying the ELF binary
>> post-compilation, it prevents accidental embedded NULs and forces
>> intentional abuse to require deliberate binary manipulation rather than
>> simple source-level tricks.
>>
>> Build tested with test modules containing both valid and invalid license
>> strings. The check correctly rejects:
>>
>>     MODULE_LICENSE("GPL\0proprietary")
>>
>> while accepting normal declarations:
>>
>>     MODULE_LICENSE("GPL")
> 
> Who will take this series? I can take the first two media patches and
> someone else can take this last patch, or I can take all, or someone
> else can take all patches. The media patches already have my 'Reviewed-by'.
> 
> Any preferences?

I will take patch 3 in modules' tree.


> 
> Regards,
> 
> 	Hans
> 
>>
>> Link: https://lwn.net/Articles/82305/ [1]
>> Suggested-by: Rusty Russell <rusty@rustcorp.com.au>
>> Signed-off-by: Kees Cook <kees@kernel.org>

Reviewed-by: Daniel Gomez <da.gomez@samsung.com>
Re: [PATCH v2 3/3] module: Add compile-time check for embedded NUL characters
Posted by Hans Verkuil 3 months ago
On 03/11/2025 09:58, Daniel Gomez wrote:
> On 03/11/2025 09.54, Hans Verkuil wrote:
>> On 10/10/2025 05:06, Kees Cook wrote:
>>> Long ago, the kernel module license checks were bypassed by embedding a
>>> NUL character in the MODULE_LICENSE() string[1]. By using a string like
>>> "GPL\0proprietary text", the kernel would only read "GPL" due to C string
>>> termination at the NUL byte, allowing proprietary modules to avoid kernel
>>> tainting and access GPL-only symbols.
>>>
>>> The MODULE_INFO() macro stores these strings in the .modinfo ELF
>>> section, and get_next_modinfo() uses strcmp()-family functions
>>> which stop at the first NUL. This split the embedded string into two
>>> separate .modinfo entries, with only the first part being processed by
>>> license_is_gpl_compatible().
>>>
>>> Add a compile-time check using static_assert that compares the full
>>> string length (sizeof - 1) against __builtin_strlen(), which stops at
>>> the first NUL. If they differ, compilation fails with a clear error
>>> message.
>>>
>>> While this check can still be circumvented by modifying the ELF binary
>>> post-compilation, it prevents accidental embedded NULs and forces
>>> intentional abuse to require deliberate binary manipulation rather than
>>> simple source-level tricks.
>>>
>>> Build tested with test modules containing both valid and invalid license
>>> strings. The check correctly rejects:
>>>
>>>     MODULE_LICENSE("GPL\0proprietary")
>>>
>>> while accepting normal declarations:
>>>
>>>     MODULE_LICENSE("GPL")
>>
>> Who will take this series? I can take the first two media patches and
>> someone else can take this last patch, or I can take all, or someone
>> else can take all patches. The media patches already have my 'Reviewed-by'.
>>
>> Any preferences?
> 
> I will take patch 3 in modules' tree.

OK, then I'll take patches 1 and 2 in the media tree.

Regards,

	Hans

> 
> 
>>
>> Regards,
>>
>> 	Hans
>>
>>>
>>> Link: https://lwn.net/Articles/82305/ [1]
>>> Suggested-by: Rusty Russell <rusty@rustcorp.com.au>
>>> Signed-off-by: Kees Cook <kees@kernel.org>
> 
> Reviewed-by: Daniel Gomez <da.gomez@samsung.com>
Re: [PATCH v2 3/3] module: Add compile-time check for embedded NUL characters
Posted by Aaron Tomlin 3 months, 2 weeks ago
On Thu, Oct 09, 2025 at 08:06:09PM -0700, Kees Cook wrote:
> Long ago, the kernel module license checks were bypassed by embedding a
> NUL character in the MODULE_LICENSE() string[1]. By using a string like
> "GPL\0proprietary text", the kernel would only read "GPL" due to C string
> termination at the NUL byte, allowing proprietary modules to avoid kernel
> tainting and access GPL-only symbols.
> 
> The MODULE_INFO() macro stores these strings in the .modinfo ELF
> section, and get_next_modinfo() uses strcmp()-family functions
> which stop at the first NUL. This split the embedded string into two
> separate .modinfo entries, with only the first part being processed by
> license_is_gpl_compatible().
> 
> Add a compile-time check using static_assert that compares the full
> string length (sizeof - 1) against __builtin_strlen(), which stops at
> the first NUL. If they differ, compilation fails with a clear error
> message.
> 
> While this check can still be circumvented by modifying the ELF binary
> post-compilation, it prevents accidental embedded NULs and forces
> intentional abuse to require deliberate binary manipulation rather than
> simple source-level tricks.
> 
> Build tested with test modules containing both valid and invalid license
> strings. The check correctly rejects:
> 
>     MODULE_LICENSE("GPL\0proprietary")
> 
> while accepting normal declarations:
> 
>     MODULE_LICENSE("GPL")
> 
> Link: https://lwn.net/Articles/82305/ [1]
> Suggested-by: Rusty Russell <rusty@rustcorp.com.au>
> Signed-off-by: Kees Cook <kees@kernel.org>
> ---
> Cc: Luis Chamberlain <mcgrof@kernel.org>
> Cc: Petr Pavlu <petr.pavlu@suse.com>
> Cc: Daniel Gomez <da.gomez@kernel.org>
> Cc: Sami Tolvanen <samitolvanen@google.com>
> Cc: <linux-modules@vger.kernel.org>
> ---
>  include/linux/moduleparam.h | 3 +++
>  1 file changed, 3 insertions(+)
> 
> diff --git a/include/linux/moduleparam.h b/include/linux/moduleparam.h
> index 6907aedc4f74..915f32f7d888 100644
> --- a/include/linux/moduleparam.h
> +++ b/include/linux/moduleparam.h
> @@ -26,6 +26,9 @@
>  
>  /* Generic info of form tag = "info" */
>  #define MODULE_INFO(tag, info)					  \
> +	static_assert(						  \
> +		sizeof(info) - 1 == __builtin_strlen(info),	  \
> +		"MODULE_INFO(" #tag ", ...) contains embedded NUL byte"); \
>  	static const char __UNIQUE_ID(modinfo)[]			  \
>  		__used __section(".modinfo") __aligned(1)		  \
>  		= __MODULE_INFO_PREFIX __stringify(tag) "=" info
> -- 
> 2.34.1
> 
> 

Nice!

Reviewed-by: Aaron Tomlin <atomlin@atomlin.com>

-- 
Aaron Tomlin
Re: [PATCH v2 3/3] module: Add compile-time check for embedded NUL characters
Posted by Petr Pavlu 4 months ago
On 10/10/25 5:06 AM, Kees Cook wrote:
> Long ago, the kernel module license checks were bypassed by embedding a
> NUL character in the MODULE_LICENSE() string[1]. By using a string like
> "GPL\0proprietary text", the kernel would only read "GPL" due to C string
> termination at the NUL byte, allowing proprietary modules to avoid kernel
> tainting and access GPL-only symbols.
> 
> The MODULE_INFO() macro stores these strings in the .modinfo ELF
> section, and get_next_modinfo() uses strcmp()-family functions
> which stop at the first NUL. This split the embedded string into two
> separate .modinfo entries, with only the first part being processed by
> license_is_gpl_compatible().
> 
> Add a compile-time check using static_assert that compares the full
> string length (sizeof - 1) against __builtin_strlen(), which stops at
> the first NUL. If they differ, compilation fails with a clear error
> message.
> 
> While this check can still be circumvented by modifying the ELF binary
> post-compilation, it prevents accidental embedded NULs and forces
> intentional abuse to require deliberate binary manipulation rather than
> simple source-level tricks.
> 
> Build tested with test modules containing both valid and invalid license
> strings. The check correctly rejects:
> 
>     MODULE_LICENSE("GPL\0proprietary")
> 
> while accepting normal declarations:
> 
>     MODULE_LICENSE("GPL")
> 
> Link: https://lwn.net/Articles/82305/ [1]
> Suggested-by: Rusty Russell <rusty@rustcorp.com.au>
> Signed-off-by: Kees Cook <kees@kernel.org>

Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>

-- 
Thanks,
Petr