This is an archive of the discontinued LLVM Phabricator instance.

-fsanitize=function: use type hashes instead of RTTI objects
ClosedPublic

Authored by MaskRay on Apr 20 2023, 12:54 AM.

Details

Summary

Currently we use RTTI objects to check type compatibility. To support non-unique
RTTI objects, commit 5745eccef54ddd3caca278d1d292a88b2281528b added a
checkTypeInfoEquality string matching to the runtime.
The scheme is inefficient.

_Z1fv:
  .long   846595819                    # jmp
  .long   .L__llvm_rtti_proxy-_Z3funv
  ...

main:
  ...
  # Load the second word (pointer to the RTTI object) and dereference it.
  movslq  4(%rsi), %rax
  movq    (%rax,%rsi), %rdx
  # Is it the desired typeinfo object?
  leaq    _ZTIFvvE(%rip), %rax
  # If not, call __ubsan_handle_function_type_mismatch_v1, which may recover if checkTypeInfoEquality allows
  cmpq    %rax, %rdx
  jne     .LBB1_2
  ...

.section        .data.rel.ro,"aw",@progbits
  .p2align        3, 0x0
.L__llvm_rtti_proxy:
  .quad   _ZTIFvvE

Let's replace the indirect _ZTI pointer with a type hash similar to
-fsanitize=kcfi.

_Z1fv:
  .long   3238382334
  .long   2772461324  # type hash

main:
  ...
  # Load the second word (callee type hash) and check whether it is expected
  cmpl    $-1522505972, -4(%rax)
  # If not, fail: call __ubsan_handle_function_type_mismatch
  jne     .LBB2_2

The RTTI object derives its name from clang::MangleContext::mangleCXXRTTI,
which uses mangleType. mangleTypeName uses mangleType as well. So the
type compatibility change is high-fidelity.

Since we no longer need RTTI pointers in
__ubsan::__ubsan_handle_function_type_mismatch_v1, let's switch it back to
version 0, the original signature before
e215996a2932ed7c472f4e94dc4345b30fd0c373 (2019).
__ubsan::__ubsan_handle_function_type_mismatch_abort is not
recoverable, so we can revert some changes from
e215996a2932ed7c472f4e94dc4345b30fd0c373.

Diff Detail

Event Timeline

MaskRay created this revision.Apr 20 2023, 12:54 AM
Herald added a project: Restricted Project. · View Herald TranscriptApr 20 2023, 12:54 AM
MaskRay requested review of this revision.Apr 20 2023, 12:54 AM
Herald added projects: Restricted Project, Restricted Project, Restricted Project. · View Herald TranscriptApr 20 2023, 12:54 AM
MaskRay updated this revision to Diff 515243.Apr 20 2023, 12:57 AM

update llvm/test/CodeGen

Should HANDLER(__ubsan_handle_function_type_mismatch,"function") be added to ubsan_minimal_runtime if this is supported in the minimal runtime?

clang/lib/CodeGen/CGExpr.cpp
5382

Would CalleeTypeHashMatch be a better name?

clang/lib/CodeGen/CodeGenFunction.h
120

Presumably the signature is different to the original v0 shouldn't it be 2; or is it effectively so long since the last one that we can reuse the original without fear?

clang/test/CodeGenCXX/catch-undef-behavior.cpp
408–409

CalleeTypeHash check?

llvm/test/CodeGen/AArch64/patchable-function-entry-bti.ll
92 ↗(On Diff #515243)

Assuming the test is the equivalent of -fpatchable-function-entry=1,1 I think this is the wrong place for the nop, I think it needs to be after the signature and the loads adjusted. For example with -fsanitize=kcfi -fpatchable-function-entries=1,1

typedef int Fptr(void);

int function(void) {
  return 0;
}

int call(Fptr* fp) {
  return fp();
}

Results in code like:

        .word   1670944012                      // @call
                                        // 0x6398950c
.Ltmp1:
        nop
call:
.Lfunc_begin1:
        .cfi_startproc
// %bb.0:                               // %entry
        ldur    w16, [x0, #-8]
        movk    w17, #50598
        movk    w17, #14001, lsl #16
        cmp     w16, w17
        b.eq    .Ltmp2
        brk     #0x8220
.Ltmp2:
        br      x0
.Lfunc_end1:

Note the NOP is after the signature, with the ldur having an offset of -8 and not the usual -4. I think you would need to make sure the signature is a branch instruction for each target for this scheme to work.

MaskRay marked 2 inline comments as done.May 15 2023, 11:56 AM

Should HANDLER(__ubsan_handle_function_type_mismatch,"function") be added to ubsan_minimal_runtime if this is supported in the minimal runtime?

Thanks for the comments.

compiler-rt/lib/ubsan_minimal/ubsan_minimal_handlers.cpp has HANDLER(function_type_mismatch, "function-type-mismatch") and with this patch clang++ -fsanitize=function -fsanitize-minimal-runtime works.

clang/lib/CodeGen/CGExpr.cpp
5382

Thanks for the suggestion. Adopted.

clang/lib/CodeGen/CodeGenFunction.h
120

The signature is identical to the original v0, so we just "downgrade" the version.

MaskRay updated this revision to Diff 522286.May 15 2023, 11:57 AM
MaskRay marked 2 inline comments as done.

rename a variable

Is possible to split the patch into smaller ones?

Is possible to split the patch into smaller ones?

The Clang CodeGen side needs to match llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp and compiler-rt/lib/ubsan, so no, I think this is the smallest unit...

I have placed logically independent parts in other patches: D148665, D148573.

samitolvanen accepted this revision.May 16 2023, 4:10 PM

Looks good to me, but maybe worth waiting for someone more familiar with compiler-rt to take a look as well.

llvm/test/CodeGen/AArch64/patchable-function-entry-bti.ll
92 ↗(On Diff #515243)

No, this looks correct to me. Note that in AsmPrinter the type hash is emitted after the patchable-function-prefix nops, while the KCFI type hash is emitted before them.

This revision is now accepted and ready to land.May 16 2023, 4:10 PM
peter.smith added inline comments.May 17 2023, 2:35 AM
llvm/test/CodeGen/AArch64/patchable-function-entry-bti.ll
92 ↗(On Diff #515243)

My concern is more along the lines of how would this function be patched if the code doing the patching were unaware of the signature. I'm not familiar with how the kernel uses the nops so it could be that this is won't be a problem in practice.

With -fsanitize=kcfi it looks like there is no difference to the code doing the patching as the nops are in the same place with respect to the function entry point and there is no fall through into the signature.

With -fsanitize=function anything patching the first nop as an instruction would need to branch over the signature (unless the first entry of the signature was a branch instruction, but that would mean a target specific signature), obviously if the nop is patched with data and isn't the entry point then this doesn't matter. The code doing the patching would also need to know how to locate the nop ahead of the signature.

MaskRay added inline comments.May 17 2023, 6:27 PM
llvm/test/CodeGen/AArch64/patchable-function-entry-bti.ll
92 ↗(On Diff #515243)

The responsibility is on the user side to ensure that when M>0, they code patches one of the M NOPs to a branch over the signature (0xc105cafe).

// -fsanitize=function -fpatchable-function-entry=3,3
.Ltmp0:
        nop
        nop
        nop    # ensure there is a branch over the signature
        .long   3238382334                      # 0xc105cafe
        .long   2772461324                      # 0xa540670c
foo:

In addition, -fpatchable-function-entry=N,M where M>0 is uncommon. -fpatchable-function-entry= didn't work with -fsanitize=function before my changes.

MaskRay marked 2 inline comments as done.May 17 2023, 6:32 PM
MaskRay added inline comments.
clang/test/CodeGenCXX/catch-undef-behavior.cpp
408–409

CalleeTypeHashPtr seems clear. Do you mean to change HashCmp below to CalleeTypeHashMatch?

MaskRay added inline comments.May 17 2023, 6:36 PM
llvm/test/CodeGen/AArch64/patchable-function-entry-bti.ll
92 ↗(On Diff #515243)

My concern is more along the lines of how would this function be patched if the code doing the patching were unaware of the signature. I'm not familiar with how the kernel uses the nops so it could be that this is won't be a problem in practice.

I think the patching code must be aware if the user decides to use -fpatchable-function-entry=N,M where M>0, otherwise it's the pilot error to use the option with -fsanitize=function.

-fsanitize=function is designed to be compatible with uninstrumented functions (used as indirect call callees) and compatible with object files, -fpatchable-function-entry=N,M functions, and regular functions. The call site must use the fixed offset unaffected by -fpatchable-function-entry=N,M.

peter.smith added inline comments.May 18 2023, 2:25 AM
clang/test/CodeGenCXX/catch-undef-behavior.cpp
408–409

Apologies; I meant the comment is stale, it still says RTTI pointer check

llvm/test/CodeGen/AArch64/patchable-function-entry-bti.ll
92 ↗(On Diff #515243)

I don't have any objections here. Just wanted to make sure that we weren't breaking any expectations.

I found one use in the kernel that uses -fpatchable-function-entry=4,2 (https://lore.kernel.org/all/20230123134603.1064407-9-mark.rutland@arm.com/)

Quoting from that:

Currently, this approach is not compatible with CLANG_CFI, as the
presence/absence of pre-function NOPs changes the offset of the
pre-function type hash, and there's no existing mechanism to ensure a
consistent offset for instrumented and uninstrumented functions. When
CLANG_CFI is enabled, the existing scheme with a global ops->func
pointer is used, and there should be no functional change. I am
currently working with others to allow the two to work together in
future (though this will liekly require updated compiler support).

I expect -fsanitize=kcfi to be used in this case over -fsanitize=functions so I don't think this will cause a problem in practice.

MaskRay updated this revision to Diff 523984.May 19 2023, 8:21 PM
MaskRay marked 2 inline comments as done.

fix a comment

MaskRay added inline comments.May 19 2023, 8:34 PM
clang/test/CodeGenCXX/catch-undef-behavior.cpp
408–409

Sorry for missing the stale comment. Fixed!

llvm/test/CodeGen/AArch64/patchable-function-entry-bti.ll
92 ↗(On Diff #515243)

I expect -fsanitize=kcfi to be used in this case over -fsanitize=functions so I don't think this will cause a problem in practice.

Yes, the kernel requires the addresses of the instrumented call sites which can only be provided by -fsanitize=kcfi. -fsanitize=function instrumented call sites are not recorded in a metadata section.
The kernel doesn't have a -fsanitize=function configuration.

For userspace programs that want to attempt both -fpatchable-function-entry=N,M where M>0 and -fsanitize=function, I expect that they need to check the -fsanitize=function signature...

This revision was landed with ongoing or failed builds.May 20 2023, 8:24 AM
This revision was automatically updated to reflect the committed changes.