Skip to content

RISC-V ABI break: x7/t2 register is used to pass the 9th argument to a function in certain cases #83111

@euspectre

Description

@euspectre

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions