Skip to content

Let 32bit machines with limited memory size to use ubsan friendly - #412

Closed
ericchancf wants to merge 4 commits into
littlekernel:masterfrom
ericchancf:master
Closed

Let 32bit machines with limited memory size to use ubsan friendly#412
ericchancf wants to merge 4 commits into
littlekernel:masterfrom
ericchancf:master

Conversation

@ericchancf

Copy link
Copy Markdown
Contributor

This patch series let 32 bit machines with limited memory size can use ubsan firendly.
Provide some changes as below:

  1. Fix ssize_t compilation error with 32 bit machine
  2. Fix compilation error which include arm cmsis with C++
  3. Add some ubsan handlers which didn't exist yet.
  4. Add test_ubsan for testing.
    becasue ubsan may cause out of memory limiation in compilcation time.
    So add a module only for testing ubsan.

format specifies type 'ssize_t' (aka 'int') but the argument has type
'ssize_t' (aka 'long') [-Werror,-Wformat]
  104 |             snprintf(s.buf, 36, "%zd", (ssize_t)val_);
      |                                  ~~~   ^~~~~~~~~~~~~
      |                                  %zd

ssize_t should be the signed variant of size_t. size_t is a
typedef to __SIZE_TYPE__ and both clang and gcc
define __SIZE_TYPE__ as unsigned int for 32-bit platforms.
But there's no __SSIZE_TYPE__ definition.
ssize_t should be signed int instead of signed long
(even though they are the same size).
And fixed the format warning which use ssize_t but format is not zd.
C++ have different behavior for const qualifier.
The const qualifier used on a declaration of a non-local non-volatile
non-template(since C++14)non-inline(since C++17) variable that is not
declared extern gives it internal linkage. This is different from C
where const file scope variables have external linkage.
So add __BEGIN_CDECLS/__END_CDECLS to prevent compilation error.

Original compilation error as below:
arm-m/CMSIS/Include/cmsis_gcc.h:149:31: error: variable '__copy_table_start__' is used but not defined in this translation unit, and cannot be defined in any other translation unit because its type does not have linkage
  149 |   extern const __copy_table_t __copy_table_start__;
      |                               ^
lk/external/arch/arm/arm-m/CMSIS/Include/cmsis_gcc.h:154:40: note: used here
  154 |   for (__copy_table_t const* pTable = &__copy_table_start__; pTable < &__copy_table_end__; ++pTable) {
      |                                        ^
lk/external/arch/arm/arm-m/CMSIS/Include/cmsis_gcc.h:150:31: error: variable '__copy_table_end__' is used but not defined in this translation unit, and cannot be defined in any other translation unit because its type does not have linkage
  150 |   extern const __copy_table_t __copy_table_end__;
      |                               ^
lk/external/arch/arm/arm-m/CMSIS/Include/cmsis_gcc.h:154:72: note: used here
  154 |   for (__copy_table_t const* pTable = &__copy_table_start__; pTable < &__copy_table_end__; ++pTable) {
      |                                                                        ^
lk/external/arch/arm/arm-m/CMSIS/Include/cmsis_gcc.h:151:31: error: variable '__zero_table_start__
' is used but not defined in this translation unit, and cannot be defined in any other translation uni
t because its type does not have linkage
  151 |   exterlinking build-gdmc/gsp/platform/gsp/debugger.mod.on
const __zero_table_t __zero_table_start__;
      |                               ^
lk/external/arch/arm/arm-m/CMSIS/Include/cmsis_gcc.h:160:40: note: used here
  160 |   for (__zero_table_t const* pTable = &__zero_table_start__; pTable < &__zero_table_end__; ++pTable) {
      |                                                                        ^
lk/external/arch/arm/arm-m/CMSIS/Include/cmsis_gcc.h:151:31: error: variable '__zero_table_start__
' is used but not defined in this translation unit, and cannot be defined in any other translation uni
t because its type does not have linkage
  151 |   exterlinking build-gdmc/gsp/platform/gsp/debugger.mod.on
const __zero_table_t __zero_table_start__;
      |                               ^
lk/external/arch/arm/arm-m/CMSIS/Include/cmsis_gcc.h:160:40: note: used here
  160 |   for (__zero_table_t const* pTable = &__zero_table_start__; pTable < &__zero_table_end__; ++pTable) {
      |                                        ^
lk/external/arch/arm/arm-m/CMSIS/Include/cmsis_gcc.h:152:31: error: variable '__zero_table_end__'
is used but not defined in this translation unit, and cannot be defined in any other translation unit
because its type does not have linkage
  152 |   extern const __zero_table_t __zero_table_end__;
      |                               ^
lk/external/arch/arm/arm-m/CMSIS/Include/cmsis_gcc.h:160:72: note: used here
  160 |   for (__zero_table_t const* pTable = &__zero_table_start__; pTable < &__zero_table_end__; ++pTable) {
      |
…rrors

Add handlers as below:
1. __ubsan_handle_function_type_mismatch
2. __ubsan_handle_alignment_assumption
3. __ubsan_handle_float_cast_overflow
Also fix the format errors for compiling and printing.

Test log is as below:

start test_ubsan_function_type_mismatch ...
===================================UBSAN error===================================
ubsan: function type mismatch in lk/lib/test_ubsan/test_ubsan.c:191:5
Call function(0x12e8d) through pointer with incompatible type 'void (*)(void)'
=================================================================================

start test_ubsan_alignment_assumption ...
alignment assumption
===================================UBSAN error===================================
ubsan: Alignment Error in lk/lib/test_ubsan/test_ubsan.c:217:11
assumption of 32 byte alignment for pointer of type 'void *' failed address is 2 aligned, misalignment offset is 3 bytes
=================================================================================
alignment assumption offset
===================================UBSAN error===================================
ubsan: Alignment Error in lk/lib/test_ubsan/test_ubsan.c:218:11
assumption of 32 byte alignment (with offset of 4 byte) for pointer of type 'void *' failed offset address is 2 aligned, misalignment offset is 31 bytes
=================================================================================

start test_ubsan_float_cast_overflow ...
===================================UBSAN error===================================
ubsan: Float Cast Overflow in lk/lib/test_ubsan/test_ubsan.c:82:12
value 'double' is outside of the range of representable value of type 'unsigned int' at 0x2001d3e8
=================================================================================
Some of platforms may have the memory limitation so can not enable
UBSAN directly. So add a test module for ubsan.
Test log is as below:

test_ubsan
Usage: test_ubsan [OPTION]
    NUM - The testcase number you want to test
    all - executing all ubsan test cases
Available ubsan test cases:
Num: testcase name
  0: add_overflow
  1: sub_overflow
  2: mul_overflow
  3: negate_overflow
  4: pointer_overflow
  5: float_cast_overflow
  6: shift_out_of_bounds_overflow
  7: shift_out_of_bounds_negative
  8: out_of_bounds_overflow
  9: out_of_bounds_underflow
 10: load_invalid_value_bool
 11: load_invalid_value_enum
 12: nullptr_deref
 13: misaligned_access
 14: object_size_mismatch
 15: function_type_mismatch
 16: nonnull_arg
 17: nonnull_return
 18: vla_bound_not_positive
 19: alignment_assumption
 20: alignment_assumption_offset
 21: invalid_builtin_clz
 22: invalid_builtin_ctz
 23: builtin_unreachable

test_ubsan all
start add_overflow ...
===================================UBSAN error===================================
ubsan: Integer overflow in lk/lib/test_ubsan/test_ubsan.c:25:9
signed integer overflow: 2147483647 + 2 can't be represented in type 'int'
=================================================================================
start sub_overflow ...
===================================UBSAN error===================================
ubsan: Integer overflow in lk/lib/test_ubsan/test_ubsan.c:32:9
signed integer overflow: -2147483648 - 2 can't be represented in type 'int'
=================================================================================
start mul_overflow ...
===================================UBSAN error===================================
ubsan: Integer overflow in lk/lib/test_ubsan/test_ubsan.c:39:9
signed integer overflow: 1073741823 * 3 can't be represented in type 'int'
=================================================================================
start negate_overflow ...
===================================UBSAN error===================================
ubsan: Negation overflow in lk/lib/test_ubsan/test_ubsan.c:45:11
Negation of -2147483648 cannot be represented in type 'int'
=================================================================================
start pointer_overflow ...
===================================UBSAN error===================================
ubsan: ptr overflow in lk/lib/test_ubsan/test_ubsan.c:60:9
ptr operation overflowed 0x2001d590 to 0x2001d58f
=================================================================================
start float_cast_overflow ...
===================================UBSAN error===================================
ubsan: Float Cast Overflow in lk/lib/test_ubsan/test_ubsan.c:68:12
value 'double' is outside of the range of representable value of type 'unsigned int' at 0x2001d580
=================================================================================
start shift_out_of_bounds_overflow ...
===================================UBSAN error===================================
ubsan: shift-oob in lk/lib/test_ubsan/test_ubsan.c:76:9
left shift of 2147483647 by 1 places cannot be represented in type 'volatile int'
=================================================================================
start shift_out_of_bounds_negative ...
===================================UBSAN error===================================
ubsan: shift-oob in lk/lib/test_ubsan/test_ubsan.c:83:9
shift exponent -1 is negative
=================================================================================
start out_of_bounds_overflow ...
===================================UBSAN error===================================
ubsan: out of bounds in lk/lib/test_ubsan/test_ubsan.c:94:5
index 4 out of range for type 'volatile int[4]' (4)
=================================================================================
start out_of_bounds_underflow ...
===================================UBSAN error===================================
ubsan: out of bounds in lk/lib/test_ubsan/test_ubsan.c:105:5
index -1 out of range for type 'volatile int[4]' (ffffffff)
=================================================================================
start load_invalid_value_bool ...
===================================UBSAN error===================================
ubsan: load invalid value in lk/lib/test_ubsan/test_ubsan.c:117:12
load of value 2, which is not a valid value for type 'bool'
=================================================================================
start load_invalid_value_enum ...
===================================UBSAN error===================================
ubsan: load invalid value in lk/lib/test_ubsan/test_ubsan.cpp:7:25
load of value 5, which is not a valid value for type 'volatile enum E'
=================================================================================
===================================UBSAN error===================================
ubsan: load invalid value in lk/lib/test_ubsan/test_ubsan.cpp:8:12
load of value 5, which is not a valid value for type 'volatile enum E'
=================================================================================
start nullptr_deref ...
===================================UBSAN error===================================
ubsan: Null pointer dereference in lk/lib/test_ubsan/test_ubsan.c:125:15
load of null pointer of type 'int'
=================================================================================
start misaligned_access ...
===================================UBSAN error===================================
ubsan: Unaligned access in lk/lib/test_ubsan/test_ubsan.c:137:10
member access within unaligned pointer 0x2001d58e of type 'struct object' (alignment 4)
=================================================================================
===================================UBSAN error===================================
ubsan: Unaligned access in lk/lib/test_ubsan/test_ubsan.c:137:10
store to unaligned pointer 0x2001d58e of type 'int' (alignment 4)
=================================================================================
start object_size_mismatch ...
===================================UBSAN error===================================
ubsan: Insufficient object size in lk/lib/test_ubsan/test_ubsan.c:148:10
member access within address 0x2001d58c with insufficient space for type 'struct object'
=================================================================================
===================================UBSAN error===================================
ubsan: Insufficient object size in lk/lib/test_ubsan/test_ubsan.c:148:10
store to address 0x2001d58c with insufficient space for type 'int'
=================================================================================
start function_type_mismatch ...
===================================UBSAN error===================================
ubsan: function type mismatch in lk/lib/test_ubsan/test_ubsan.c:155:5
Call function(0x12d0d) through pointer with incompatible type 'void (*)(void)'
=================================================================================
start nonnull_arg ...
===================================UBSAN error===================================
ubsan: nonnull-argument in lk/lib/test_ubsan/test_ubsan.c:161:18
null pointer passed as argument 1, specified non-null
=================================================================================
start nonnull_return ...
===================================UBSAN error===================================
ubsan: nonnull return in lk/lib/test_ubsan/test_ubsan.c:164:16
Returning a null pointer from function declared to never return null
=================================================================================
start vla_bound_not_positive ...
===================================UBSAN error===================================
ubsan: vla bound not positive in lk/lib/test_ubsan/test_ubsan.c:175:13
vla bound not positive (0)
=================================================================================
start alignment_assumption ...
===================================UBSAN error===================================
ubsan: Alignment Error in lk/lib/test_ubsan/test_ubsan.c:195:11
assumption of 32 byte alignment for pointer of type 'void *' failed address is 2 aligned, misalignment offset is 3 bytes
=================================================================================
start alignment_assumption_offset ...
===================================UBSAN error===================================
ubsan: Alignment Error in lk/lib/test_ubsan/test_ubsan.c:202:11
assumption of 32 byte alignment (with offset of 4 byte) for pointer of type 'void *' failed offset address is 2 aligned, misalignment offset is 31 bytes
=================================================================================
start invalid_builtin_clz ...
===================================UBSAN error===================================
ubsan: invalid builtin in lk/lib/test_ubsan/test_ubsan.c:208:29
Passed 0 to clz()
=================================================================================
start invalid_builtin_ctz ...
===================================UBSAN error===================================
ubsan: invalid builtin in lk/lib/test_ubsan/test_ubsan.c:213:29
Passed 0 to ctz()
=================================================================================
start builtin_unreachable ...
===================================UBSAN error===================================
ubsan: execution reached an unreachable program point in lk/lib/test_ubsan/test_ubsan.c:218:5
=================================================================================
#ifdef __LP64__
typedef signed long int ssize_t;
#else
typedef signed int ssize_t;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this isn't really always true, it varies by architecture. For LK I've bee forcing size_t to be a long always, even if it's against the default the compiler sets it to, and it works reasonably well as long as the code is mostly using stuff built in tree.

Is there a strong need for this? If so we may be able to set it for particular architectures that its an issue with (such as linux-x86, where i know size_t is unsigned int).

@travisg travisg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move the ubsan test module under /lib/ubsan/test instead of /lib/ubsan_test?

Otherwise looks pretty good except for the question about ssize_t.

@travisg

travisg commented Nov 4, 2024

Copy link
Copy Markdown
Member

ping.

travisg added a commit that referenced this pull request Aug 23, 2026
ssize_t was hard coded to signed long int, which is not the signed
counterpart of size_t everywhere: gcc defines __SIZE_TYPE__ as unsigned
int on arm, riscv32, or1k and microblaze but as long unsigned int on
i386 and m68k, so no single hard coded type is right across the tree.
Wherever the two disagreed, printing an ssize_t with %zd tripped
-Werror=format= and the code worked around it with %ld instead, which
is then wrong on the other half of the targets.

Take the type from the compiler instead. __PTRDIFF_TYPE__ is exactly
the signed counterpart of __SIZE_TYPE__ on every target LK builds for
(verified against the arm, aarch64, riscv32, riscv64, i386, x86_64,
m68k, microblaze and or1k gcc toolchains, and against clang), so %zd
is now correct everywhere and the %ld workarounds go away.

Salvaged from PR #412. Changes from the original PR:

 - derive the type rather than switching on __LP64__, which was still
   wrong on i386 and m68k.
 - use __PTRDIFF_TYPE__ directly instead of the "#define unsigned
   signed" token rewriting trick around __SIZE_TYPE__.
 - extend the %zd fixes to code added since the PR was posted
   (app/cdcserialtest, app/irc, app/moot, app/ndebugtest,
   dev/block/ahci, dev/virtio/block, lib/elf, lib/klog, lib/ptable,
   lib/sysparam, platform/stm32f7xx, platform/zynq).
 - lib/uefi/blockio_protocols.cpp printed a size_t with %ld, which was
   wrong before this change too; it becomes %zu.

Verified with scripts/buildall -q -e, which covers the 32 bit targets
where a %zd/%ld mistake actually shows up.

Co-authored-by: Travis Geiselbrecht <geist@foobox.com>
travisg added a commit that referenced this pull request Aug 23, 2026
In C++ a const file scope variable that is not declared extern has
internal linkage, unlike C where it has external linkage. The
__copy_table_start__ / __zero_table_start__ style linker symbols that
cmsis_gcc.h declares as "extern const __copy_table_t" therefore end up
with a type that has no linkage when the header is included from C++,
and the compiler rejects taking their address:

  cmsis_gcc.h:149:31: error: variable '__copy_table_start__' is used
  but not defined in this translation unit, and cannot be defined in
  any other translation unit because its type does not have linkage

Wrap the header in __BEGIN_CDECLS / __END_CDECLS, which lk/compiler.h
(already included just above) provides.

Salvaged from PR #412, unchanged apart from this commit message.

Co-authored-by: Travis Geiselbrecht <geist@foobox.com>
travisg added a commit that referenced this pull request Aug 23, 2026
Add handlers for function_type_mismatch, alignment_assumption and
float_cast_overflow, and fix several format strings (missing newlines,
misspellings, %ld used for ssize_t arguments).

Salvaged from PR #412. Changes from the original PR:

 - compute the reported alignment with (x & -x) rather than
   1 << ffs(x), which reported twice the real alignment, since lk's
   ffs() is the 1-based builtin.
 - use %zd/%zu for the ssize_t/size_t arguments so the handlers build
   on 32 bit targets.
 - __ubsan_handle_float_cast_overflow was printing the source type name
   under a "value %s" label and the opaque value handle as an address.
   The handle's representation depends on the source type and
   val::to_string() only decodes integers, so report the conversion
   that overflowed and drop the bogus value.

Also drop the note saying __ubsan_handle_function_type_mismatch had
been dropped from both compilers. Clang 18 still emits a call to it
from a plain -fsanitize=undefined build of C code, which is what this
handler is for; gcc does not emit it at all.

Co-authored-by: Travis Geiselbrecht <geist@foobox.com>
travisg added a commit that referenced this pull request Aug 23, 2026
…ndlers

The tests intentionally trigger each category of undefined behavior, so
they are an operator driven console command (test_ubsan <n>|all) rather
than a pass/fail unittest -- "all" ends in a panic on the
__builtin_unreachable case, so it must not run from `ut all`. Drive it
by hand, e.g.

  UBSAN=1 scripts/do-qemuarm -6 -A 'lk.autorun=test_ubsan all'

The module lives in lib/ubsan/test and is pulled in via the standard
test submodule mechanism, so it is built when UBSAN=1 and WITH_TESTS=1.

Salvaged from PR #412. Changes from the original PR: moved from
lib/test_ubsan to lib/ubsan/test per review feedback, renamed the
sources to match, and dropped the per module -fsanitize=undefined,
which lib/ubsan already adds globally. The module carries
MODULE_OPTIONS := float for the float_cast_overflow case, and silences
-Wunused-but-set-variable and -Wstrict-aliasing, which the deliberately
undefined code trips.

Note that gcc emits checks for only part of this: the
function_type_mismatch, alignment_assumption, float_cast_overflow,
pointer_overflow and builtin clz/ctz cases fire under clang only, so
exercising the three handlers added in the previous commit needs
TOOLCHAIN=clang.

Co-authored-by: Travis Geiselbrecht <geist@foobox.com>
@travisg

travisg commented Aug 23, 2026

Copy link
Copy Markdown
Member

Thanks for this, and apologies for the long delay. I've landed it as four commits (8c61f61..f8dd002) with your authorship preserved.

The core of it — making ssize_t the real signed counterpart of size_t instead of a hardcoded long — was the right call, and it removes the %zd warning class at the root. Changes from the PR as posted:

  • typedef __PTRDIFF_TYPE__ ssize_t; rather than #define unsigned signed around __SIZE_TYPE__. It's the same type on every target LK builds for — I checked against the arm, aarch64, riscv32, riscv64, i386, x86_64, m68k, microblaze and or1k gcc toolchains and against clang — and it avoids the token-rewriting trick. (The __LP64__ switch in the first revision of the PR would still have been wrong on i386 and m68k, where __SIZE_TYPE__ is long unsigned int.)
  • Alignment handler: the reported alignment was 1 << ffs(real_ptr), which is twice the real value, since lk's ffs() is the 1-based __builtin_ffs. Now real_ptr & -real_ptr; a pointer of 0x3 correctly reports 1-byte alignment.
  • __ubsan_handle_float_cast_overflow was printing the source type name under a "value %s" label and the opaque value handle as an address. The handle's representation depends on the source type and val::to_string() only decodes integers, so it now reports the conversion that overflowed instead.
  • test_ubsan moved to lib/ubsan/test as discussed in review, wired up via the normal MODULE_OPTIONS := test mechanism.
  • The %zd sweep was extended to code added since you posted: app/cdcserialtest, app/irc, app/moot, app/ndebugtest, dev/block/ahci, dev/virtio/block, lib/elf, lib/klog, lib/ptable, lib/sysparam, platform/stm32f7xx, platform/zynq. lib/uefi/blockio_protocols.cpp was printing a size_t with %ld, which was wrong beforehand too.
  • Removed the stale note saying __ubsan_handle_function_type_mismatch had been dropped from both compilers — clang 18 still emits a call to it from a plain -fsanitize=undefined build of C, so your handler is doing real work there.

Verified with scripts/buildall -q -e across all 58 projects (the 32-bit targets are the ones that matter for this), the clang and UBSAN buildall passes, and run-qemu-boot-tests.py on six architectures with and without --ubsan. Each commit builds standalone — worth noting the ssize_t change on its own broke UBSAN=1 builds until the three %ld sites in ubsan.cpp moved into that same commit.

One thing worth recording for anyone reading later: gcc doesn't emit checks for function_type_mismatch, alignment_assumption or float_cast_overflow, so the three handlers you added only fire under clang. UBSAN=1 TOOLCHAIN=clang LD=ld.lld scripts/do-qemuarm -6 -A 'lk.autorun=test_ubsan all' exercises them.

Closing in favour of the merged commits.

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.

2 participants