linux-kernellinux-device-drivergnu-assembler

How to address ".text+XXX: 'naked' return found in RETHUNK build" in assembly part of a Linux driver?


I am porting an x86_64-specific Linux kernel driver to work with recent (6.0+) kernel versions in a recent distro (Fedora 39). The module has some of its parts written in assembler (it has to interact with Intel VT-x), which are then linked to the rest of the driver.

The build chain is based on GNU Binutils (GCC etc.)

When the assembler part is linked to the rest of the module, objtool complains at the object code generated from the assembler file:

AS [M] /path/common/entry.o

/path/common/entry.o: warning: objtool: .text+XXXX: 'naked' return found in RETHUNK build

I believe what is expected here is that a compiler would generate additional security checks around the ret instruction to catch ROP-attacks. Given that this code is hand-written, of course it lacks such decorations.

The question: how should the "rethunk" part look like? Can it be statically added into the input assembler file, or is it so dynamic in its nature that I should instead suppress the warning and move on?


Solution

  • Here is what I found after some research.

    objtool is a static control flow analysis tool used during the kernel build to scan the object code for undesirable patterns, such as missing frame stack setup/teardown sequences. The documentation does not seem to be complete, as it does not explain the types of checks the tool applies. Nothing is said about this ret situation either.

    A "naked ret" is an x86 instruction ret without additional assembler decorations designed to fight side-channel speculation execution attacks.

    Many other assembler files in the kernel use a macro called RET instead of a naked ret. Depending on what build flags are used, this macro unfolds in one of three different ways

    #if defined(CONFIG_RETHUNK) && !defined(__DISABLE_EXPORTS) && !defined(BUILD_VDSO)
    #define RET jmp __x86_return_thunk
    #else /* CONFIG_RETPOLINE */
    #ifdef CONFIG_SLS
    #define RET ret; int3
    #else
    #define RET ret
    #endif
    #endif /* CONFIG_RETPOLINE */
    
    

    So it can be treated as a plain old single ret, a ret followed by an unconditional interrupt (meant to abort linear speculative execution), or a seemingly strange direct jump to __x86_return_thunk. The last option is the most involved one.

    The label __x86_return_thunk still eventually leads to a ret. But the way it is done now is to prevent the processor's branch prediction mechanism to learn the control flow patterns. This way, an attacker has less chances to misuse them to recover information via a side channel timing attack.


    So, a solution to my original problem may be in one of the following:

    1. Use the RET macro instead of a plain ret.
    2. Pass different flags to objtool so that my assembler file is compiled against a different set of checks.