Skip to content

shared: replace packed-struct get/put_unaligned with memcpy#445

Open
rawrmonster17 wants to merge 1 commit into
kmod-project:masterfrom
rawrmonster17:shared-fix-get-unaligned-memcpy
Open

shared: replace packed-struct get/put_unaligned with memcpy#445
rawrmonster17 wants to merge 1 commit into
kmod-project:masterfrom
rawrmonster17:shared-fix-get-unaligned-memcpy

Conversation

@rawrmonster17

Copy link
Copy Markdown

Fixes #338.

Problem

The current get_unaligned/put_unaligned macros in shared/util.h use a packed-struct pointer-cast pattern:

#define get_unaligned(ptr)                       \
	({                                       \
		struct __attribute__((packed)) { \
			typeof(*(ptr)) __v;      \
		} *__p = (typeof(__p))(ptr);     \
		__p->__v;                        \
	})

This is not guaranteed portable. With GCC 14 at -O2 on RISC-V (and historically on MIPS/SPARC strict-alignment targets), the auto-vectoriser can emit aligned vector loads for the surrounding loop in hash_superfast(), causing SIGBUS on hardware that enforces natural alignment (reported in #338).

The bug affects shared/hash.c (4 call sites in the main and fall-through loops) and libkmod/libkmod-index.c (1 call site reading a uint32_t from a potentially-unaligned address). put_unaligned is defined but currently unused.

Fix

Replace both macros with memcpy-based versions:

#define get_unaligned(ptr)                                               \
	__extension__({                                                  \
		typeof(*(ptr)) __v;                                      \
		memcpy(&__v, (ptr), sizeof(__v));                        \
		__v;                                                     \
	})

#define put_unaligned(val, ptr)                                          \
	do {                                                             \
		typeof(*(ptr)) __v = (val);                              \
		memcpy((ptr), &__v, sizeof(__v));                        \
	} while (0)

memcpy with a compile-time-constant size is recognised by GCC, clang, and ICC and lowered to the same efficient inline sequence as the pointer cast on x86/x86-64, while remaining correct and standards-compliant on all architectures. This is the same approach used by the Linux kernel (include/linux/unaligned.h).

<string.h> is already included by shared/util.h. The macro signatures are unchanged, so all call sites in hash.c and libkmod-index.c are unaffected.

Testing

  • Built with meson setup build --buildtype=plain && ninja -C buildzero errors, zero warnings on all changed translation units.
  • Ran a dedicated self-check covering all 5 call-site patterns (uint16_t at +1 offset, uint16_t at +3 offset, uint32_t at +1 offset, put uint16_t round-trip, put uint32_t round-trip) with intentionally unaligned pointers — all pass.

@lucasdemarchi

Copy link
Copy Markdown
Contributor

This is the same approach used by the Linux kernel (include/linux/unaligned.h).

Relevant kernel commit: a339671db64b ("vdso: Switch get/put_unaligned() from packed struct to memcpy()")

@lucasdemarchi

Copy link
Copy Markdown
Contributor

Please use a real user for commit author and add your s-o-b.

It doesn't pass a warning-free build:

../shared/util.h:78:24: error: passing argument 1 of ‘memcpy’ discards ‘const’ qualifier from pointer target type [-Werror=discarded-qualifiers]
   78 |                 memcpy(&__v, (ptr), sizeof(__v));                        \
      |                        ^~~~

and also triggers array out of bounds:

In function ‘memcpy’,
    inlined from ‘read_u32_mm’ at ../libkmod/libkmod-index.c:662:6,
    inlined from ‘index_mm_open’ at ../libkmod/libkmod-index.c:791:16:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:29:10: error: array subscript 0 is outside array bounds of ‘const uint8_t[0]’ {aka ‘const unsigned char[]’} [-Werror=array-bounds=]
   29 |   return __builtin___memcpy_chk (__dest, __src, __len,
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   30 |                                  __glibc_objsize0 (__dest));
      |                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~
In function ‘index_mm_open’:
cc1: note: source object is likely at address zero
cc1: all warnings being treated as errors
[25/34] Compiling C object kmod.p/tools_depmod.c.o
ninja: build stopped: subcommand failed.

I think adopting a exact same macro as the kernel uses should be fine. This simplified version doesn't work as expected.

The packed-struct pointer-cast pattern used by get_unaligned()/put_unaligned()
is not portable. With GCC 14 at -O2 on RISC-V, and historically on other
strict-alignment architectures, surrounding code can still be compiled into
aligned accesses and fault at runtime.

Switch the helpers to the same memcpy-based pattern the Linux kernel uses:
strip qualifiers from the temporary object type, then use __builtin_memcpy()
with void * casts for the load/store. This preserves the existing macro API
while avoiding the discarded-qualifiers and array-bounds build failures
reported in review.

Fixes: kmod-project#338
Signed-off-by: Chris Beck <beck.chris88@outlook.com>
@rawrmonster17 rawrmonster17 force-pushed the shared-fix-get-unaligned-memcpy branch from b62339a to b5ce7e7 Compare June 21, 2026 07:22
@rawrmonster17

Copy link
Copy Markdown
Author

Updated the PR branch.

  • Rewrote the commit with a real author and Signed-off-by: Chris Beck <beck.chris88@outlook.com>.
  • Switched the helpers to the kernel-style pattern using __typeof_unqual__ plus __builtin_memcpy() with void * casts, rather than the simplified memcpy() form.
  • Rebuilt on Linux with gcc using -O2 -Werror; the build now passes warning-free.
  • Also rechecked with a 32-bit build and a small targeted self-check for the affected unaligned read/write patterns.

No call sites changed; the update is still localized to shared/util.h.

@evelikov

Copy link
Copy Markdown
Collaborator

Thanks for the PR @rawrmonster17 - few questions/suggestions, in random order:

  • Please double-check our readme and contributing docs - namely the "Hacking" and "Commit trailers" sections.

  • Do share the self-check program and methodology used

  • Are the new extensions/built-ins available on our minimum gcc/clang versions, should we update the readme? I think we can omit the __extensions__ keyword.

  • In the commit message let's reference the kernel commit and highlight on the strict-aliasing issue, listing the RISC-V improvements as a "bonus".

@lucasdemarchi

Copy link
Copy Markdown
Contributor

typeof_unqual is gcc >= 14 only. Also, even after that, it doesn't seem the generated code is really the same.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bus error when running kmod on architecture that does not support unaligned access

3 participants