-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Description
According to the RISC-V ABI, the first 8 integer arguments of a function are passed in registers a0-a7, the 9th and subsequent ones go on stack. It seems, clang does not always conform to that.
While investigating a crash in the Linux kernel built with clang 17.0.6, I found that x7/t2 register rather than the stack was used to pass the 9th argument, mpri
, to __find_rr_leaf() function from net/ipv6/route.c.
Here is a reduced and simplified version of the preprocessed net/ipv6/route.c that demonstrates the issue:
test-arg9.c
It can be compiled as follows (the options were extracted from the kernel build):
clang --target=riscv64-linux-gnu -fintegrated-as \
-Werror=unknown-warning-option -Werror=ignored-optimization-argument -Werror=option-ignored \
-Werror=unused-command-line-argument \
-std=gnu11 -fshort-wchar -funsigned-char -fno-common -fno-PIE \
-fno-strict-aliasing -mabi=lp64 -march=rv64imac_zihintpause \
-mno-save-restore -mcmodel=medany \
-fno-asynchronous-unwind-tables -fno-unwind-tables -fno-delete-null-pointer-checks \
-O2 \
-fstack-protector-strong -fno-omit-frame-pointer -fno-optimize-sibling-calls \
-ftrivial-auto-var-init=zero \
-falign-functions=4 -fpatchable-function-entry=12 \
-fstrict-flex-arrays=3 -fno-strict-overflow -fno-stack-check \
-Wall -Wundef -Werror=implicit-function-declaration \
-Werror=implicit-int -Werror=return-type -Werror=strict-prototypes \
-Wno-format-security -Wno-trigraphs -Wno-frame-address -Wno-address-of-packed-member \
-Wmissing-declarations -Wmissing-prototypes \
-Wframe-larger-than=2048 -Wno-gnu -Wno-unused-but-set-variable -Wno-unused-const-variable \
-Wvla -Wno-pointer-sign -Wcast-function-type -Wimplicit-fallthrough \
-Werror=date-time -Werror=incompatible-pointer-types -Wenum-conversion \
-Wno-unused-but-set-variable -Wno-unused-const-variable -Wno-pointer-to-enum-cast \
-Wno-tautological-constant-out-of-range-compare -Wno-unaligned-access -Wno-cast-function-type-strict \
-Wno-missing-field-initializers -Wno-type-limits -Wno-shift-negative-value -Wno-initializer-overrides \
-Wno-sign-compare -Wno-unused-value -Wno-parentheses-equality -Wno-self-assign \
-g -c -o test-arg9.o test-arg9.c
clang: version 17.0.6 (https://github.com/llvm/llvm-project.git 6009708)
Target: riscv64-unknown-linux-gnu
A disassembly of test-arg9.o:
test-arg9.disasm
Here are the portions of __find_rr_leaf()
(callee) and fib6_table_lookup()
(caller) where t2 is used to pass the 9th argument to the function:
fib6_table_lookup():
...
00000000000000c4 <.LBB0_9>:
c4: 00ccab83 lw s7,12(s9)
c8: f8043423 sd zero,-120(s0)
cc: f9b42223 sw s11,-124(s0)
d0: f8840713 addi a4,s0,-120
d4: f8340893 addi a7,s0,-125
d8: f8440393 addi t2,s0,-124 // <<< here
dc: 8566 mv a0,s9
de: 4581 li a1,0
e0: 865e mv a2,s7
e2: 86da mv a3,s6
e4: 87ce mv a5,s3
e6: 884a mv a6,s2
e8: 00000097 auipc ra,0x0
e8: R_RISCV_CALL_PLT __find_rr_leaf
e8: R_RISCV_RELAX *ABS*
ec: 000080e7 jalr ra # e8 <.LBB0_9+0x24>
...
__find_rr_leaf():
...
1e2: 891e mv s2,t2
...
00000000000002b2 <.LBB1_16>:
2b2: 00092503 lw a0,0(s2) // <<< "if (m > *mpri)" in find_match()
2b6: 01455063 bge a0,s4,2b6 <.LBB1_16+0x4>
2b6: R_RISCV_BRANCH .LBB1_4
2ba: 0d05 addi s10,s10,1
2bc: 001d3513 seqz a0,s10
2c0: f4843583 ld a1,-184(s0)
2c4: 00a58023 sb a0,0(a1)
2c8: 01492023 sw s4,0(s2) // <<< "*mpri = m;" in find_match()
2cc: a001 j 2cc <.LBB1_16+0x1a>
2cc: R_RISCV_RVC_JUMP .LBB1_3
...
This looks like a RISC-V ABI break.
Ftrace, for example, relies on the fact that the temporary registers like t0-t2 can be safely clobbered at the beginning of the function. So, if dynamic Ftrace is enabled for __find_rr_leaf() in the kernel, t2 could contain anything when the function tries dereferencing its 9th argument, mpri
, hence the kernel crashes.
I do not think this issue has security implications, so, I am reporting it here.