ndk icon indicating copy to clipboard operation
ndk copied to clipboard

[BUG] False positive vptr errors with shared ubsan runtimes(which is the default)

Open CoolCaicaixian opened this issue 1 year ago • 21 comments

Description

Env Info:

【Compile Sdk Version】33 【NDK Version】R21e 【JDK】Java 8

Error Info:

2024-08-29 09:55:10.324 18568-18568 app_process64           com.cwx.inanna_android             I  /Users/cwx/work/codes/gitCodes/inanna/inanna-framework/demos/android/inanna-android/app/src/main/cpp/native-lib.cpp:42:22: runtime error: member call on address 0x0041793d7bf0 which does not point to an object of type 'Base'
2024-08-29 09:55:10.324 18568-18568 app_process64           com.cwx.inanna_android             I  0x0041793d7bf0: note: object is of type 'Derived'
2024-08-29 09:55:10.324 18568-18568 app_process64           com.cwx.inanna_android             I   00 00 00 00  f0 95 db 13 71 00 00 00  00 00 00 00 be be be be  02 11 00 00 10 00 00 00  5b 27 00 00
2024-08-29 09:55:10.324 18568-18568 app_process64           com.cwx.inanna_android             I                ^~~~~~~~~~~~~~~~~~~~~~~
2024-08-29 09:55:10.324 18568-18568 app_process64           com.cwx.inanna_android             I                vptr for 'Derived'

C++ Code:

// Base.h
class Base {
public:
    virtual ~Base();
    virtual int foo();  
    int bar();         
};

// Base.cpp
Base::~Base()
{
}
int Base::foo()
{
    return 1;
}
int Base::bar() {
    return 2;
}

// Derived.h
class Derived : public Base {
public:
    int foo() override; 
};

// Derived.cpp
int Derived::foo() {
    return bar() + Base::foo();
}

int init() {
    Base *pBase = new Derived();
    int ret = pBase->foo();
    delete pBase;
    return ret;
}

### cmake options:

target_compile_options(inanna_android PUBLIC -fsanitize=address -fno-omit-frame-pointer)
target_link_options(inanna_android PUBLIC -fsanitize=address)
target_compile_options(inanna_android PUBLIC -fsanitize=undefined -fno-sanitize-recover=undefined)
target_link_options(inanna_android PUBLIC -fsanitize=undefined -fno-sanitize-recover=undefined)

described:

When I turned on one of the options separately, it worked well.Or if I turn off the vptr check, it can work well,but when i both open ASAN and UBASAN,the error coming...

Affected versions

Canary

Canary version

NDKr21e

Host OS

Mac

Host OS version

14.2.1

Affected ABIs

arm64-v8a

Build system

CMake

Other build system

No response

minSdkVersion

24

Device API level

No response

CoolCaicaixian avatar Aug 30 '24 01:08 CoolCaicaixian

vptr check is part of ubsan, not sure why enabling ubsan doesn't trigger it. You might want to open a bug in llvm-project/issues for clarification

appujee avatar Aug 30 '24 16:08 appujee

vptr check is part of ubsan, not sure why enabling ubsan doesn't trigger it. You might want to open a bug in llvm-project/issues for clarification

but asan*.so is in NDK

CoolCaicaixian avatar Sep 02 '24 10:09 CoolCaicaixian

vptr check is part of ubsan, not sure why enabling ubsan doesn't trigger it. You might want to open a bug in llvm-project/issues for clarification

can i make vpt check no crash in android by use wrap.sh?

CoolCaicaixian avatar Sep 03 '24 11:09 CoolCaicaixian

NDKr21e

That NDK is three and a half years old (and is based on an LLVM that's nearly five). There's a pretty good chance that the bug has been fixed since then. Have a look at https://github.com/android/ndk-samples/tree/main/sanitizers for the recommended way to use ubsan in apps. Note that ASan isn't supported any more, since HWASan is better in almost every way: http://go/android-dev/ndk/guides/memory-debug

If you update to NDK r27 and UBSan still isn't able to catch the bug without ASan, attach a complete test case and we can re-open. It may just be the case that UBSan isn't able to catch this bug in every case though. There are a number of UBSan checks that can't be caught 100% reliably, but idk if this is one of them.

DanAlbert avatar Sep 03 '24 22:09 DanAlbert

恩德克尔21e

该 NDK 已有三年半历史(并且基于近五年的 LLVM)。从那时起,该错误很有可能已经得到修复。请查看https://github.com/android/ndk-samples/tree/main/sanitizers,了解在应用中使用 ubsan 的推荐方法。请注意,ASan 不再受支持,因为 HWASan 几乎在各个方面都更胜一筹:http://go/android-dev/ndk/guides/memory-debug

如果您更新到 NDK r27 并且 UBSan 仍然无法在没有 ASan 的情况下捕获错误,请附上完整的测试用例,我们可以重新打开。不过,UBSan 可能并不是在所有情况下都能捕获此错误。有许多 UBSan 检查无法 100% 可靠地捕获,但我不知道这是否是其中之一。 截屏2024-09-04 10 35 48

I tried to upgrade to NDK r27, but there was still a problem.😢 I'm sorry, there may be problems with my previous statement. This crash has nothing to do with whether to turn on ASan. The main problem is UBSan's vpr check. If I turn on the compilation options below, a crash will occur: -fsanitize=undefined -fno-sanitize-recover=undefined But when I turned off the vpr check, everything was fine: -fsanitize=undefined -fno-sanitize-recover=undefined -fno-sanitize=vptr

CoolCaicaixian avatar Sep 04 '24 02:09 CoolCaicaixian

inanna-android.zip

Here is my demo,thanks

CoolCaicaixian avatar Sep 04 '24 02:09 CoolCaicaixian

If I turn on the compilation options below, a crash will occur: -fsanitize=undefined -fno-sanitize-recover=undefined But when I turned off the vpr check, everything was fine: -fsanitize=undefined -fno-sanitize-recover=undefined -fno-sanitize=vptr

Isn't this is the intended behavior? this bug is specifically caught by vptr sanitizer.

appujee avatar Sep 04 '24 15:09 appujee

Yeah, that's what -fno-sanitize-recover does: UBSan failures will crash the program.

DanAlbert avatar Sep 04 '24 19:09 DanAlbert

If I turn on the compilation options below, a crash will occur: -fsanitize=undefined -fno-sanitize-recover=undefined But when I turned off the vpr check, everything was fine: -fsanitize=undefined -fno-sanitize-recover=undefined -fno-sanitize=vptr

Isn't this is the intended behavior? this bug is specifically caught by vptr sanitizer.

You can take a look at the C++ code I attached at the beginning. This is the most common use of C++ polymorphism and should not be detected as an error; and I work normally on other platforms

CoolCaicaixian avatar Sep 05 '24 01:09 CoolCaicaixian

Yeah, that's what -fno-sanitize-recover does: UBSan failures will crash the program.

But my code should not be detected as errors

CoolCaicaixian avatar Sep 05 '24 01:09 CoolCaicaixian

Are you using fvisibility flag somewhere? there is a similar bug reported in: https://bugs.llvm.org/show_bug.cgi?id=39191

appujee avatar Sep 05 '24 05:09 appujee

Are you using fvisibility flag somewhere? there is a similar bug reported in: https://bugs.llvm.org/show_bug.cgi?id=39191

In fact, all my inheritances are in the same library, I didn't make calls across libraries, and I didn't proactively turn off visibility

CoolCaicaixian avatar Sep 05 '24 05:09 CoolCaicaixian

Are you using fvisibility flag somewhere? there is a similar bug reported in: https://bugs.llvm.org/show_bug.cgi?id=39191

You can download the zip I sent earlier and check my compilation options

CoolCaicaixian avatar Sep 05 '24 05:09 CoolCaicaixian

As the current behavior of vptr relates to open source implementation of this sanitizer, it is better to create an issue in the llvm-project/issues repo.

appujee avatar Sep 05 '24 20:09 appujee

As the current behavior of vptr relates to open source implementation of this sanitizer, it is better to create an issue in the llvm-project/issues repo.

I have already asked a question on this page, but no one responded😢

CoolCaicaixian avatar Sep 06 '24 01:09 CoolCaicaixian

Link?

DanAlbert avatar Sep 06 '24 19:09 DanAlbert

Link to what?

CoolCaicaixian avatar Sep 07 '24 09:09 CoolCaicaixian

Link to what?

The other bug you filed that you said has no response. We may be able to help with that.

DanAlbert avatar Sep 09 '24 20:09 DanAlbert

Link to what?

The other bug you filed that you said has no response. We may be able to help with that.

here:https://github.com/llvm/llvm-project/issues/106933 plz.

CoolCaicaixian avatar Sep 10 '24 01:09 CoolCaicaixian

Adding some folks from the sanitizer team: @eugenis @fmayer

I was able to reproduce this on Android with the following command (nb: also push libc++_shared.so.)

$ /usr/local/google/work/android-ndk-r27/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android35-clang++ san.cpp -fsanitize=undefined 

Adding -static-libsan to use the static libubsan library seems fine. This did not reproduce on Linux with both shared and static ubsan runtimes.

pirama-arumuga-nainar avatar Sep 11 '24 06:09 pirama-arumuga-nainar

Reduced test case:

struct Base {
    virtual int foo();
};

int Base::foo() {
    return 1;
}

int main() {
    Base obj;
    obj.foo();
    return 0;
}
adb shell 'rm -fr /data/local/tmp/*' && /x/android-ndk-r27/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android35-clang++ -O2 san.cpp -fsanitize=undefined && adb push /x/android-ndk-r27/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/x86_64-linux-android/libc++_shared.so /data/local/tmp && adb push /x/android-ndk-r27/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/18/lib/linux/libclang_rt.ubsan_standalone-x86_64-android.so /data/local/tmp && adb push a.out /data/local/tmp && adb shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/a.out
/x/android-ndk-r27/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/x86_64-linux-android/libc++_shared.so: 1 file pushed, 0 skipped. 334.2 MB/s (1617608 bytes in 0.005s)
/x/android-ndk-r27/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/18/lib/linux/libclang_rt.ubsan_standalone-x86_64-android.so: 1 file pushed, 0 skipped. 273.2 MB/s (870032 bytes in 0.003s)
a.out: 1 file pushed, 0 skipped. 69.6 MB/s (6376 bytes in 0.000s)
san.cpp:11:9: runtime error: member call on address 0x7ffc8754a5b0 which does not point to an object of type 'Base'
0x7ffc8754a5b0: note: object is of type 'Base'
 58 5b 00 00  a0 09 9e 37 58 5b 00 00  83 ec 0f de d7 7e 00 00  00 00 00 00 00 00 00 00  00 00 00 00
              ^~~~~~~~~~~~~~~~~~~~~~~
              vptr for 'Base'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior san.cpp:11:9 in 

Presumably the problem here is that the UBSan runtime is referencing libc++[abi] symbols and relying on those symbols having the same address across the relevant modules of code. For example:

$ nm a.out | grep class_type_info | c++filt
                 U vtable for __cxxabiv1::__class_type_info

$ nm /x/android-ndk-r27/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/18/lib/linux/libclang_rt.ubsan_standalone_cxx-x86_64-android.a | grep class_type_info | c++filt
0000000000000000 t findBaseAtOffset(__cxxabiv1::__class_type_info const*, long)
0000000000000000 t isDerivedFromAtOffset(__cxxabiv1::__class_type_info const*, __cxxabiv1::__class_type_info const*, long)
                 U typeinfo for __cxxabiv1::__class_type_info
                 U typeinfo for __cxxabiv1::__si_class_type_info
                 U typeinfo for __cxxabiv1::__vmi_class_type_info

The vtable for Base refers to __cxxabiv1::__class_type_info's vtable, and the UBSan runtime refers to the typeinfo for __cxxabiv1::__class_type_info, but the two __class_type_info classes are (probably) considered different classes because the copy in the shared ubsan runtime isn't exported.

I think the Android LLVM's glibc build turns off this part of UBSan (SANITIZER_ALLOW_CXXABI is OFF):

https://android.googlesource.com/toolchain/llvm_android/+/refs/heads/llvm-r536225-release/src/llvm_android/base_builders.py#888

The clang-19 on my machine (Rodete/gLinux/Debian), OTOH, has a UBSan shared library that imports the C++ ABI symbols from libstdc++.so.6:

$ readelf -d /usr/lib/llvm-19/lib/clang/19/lib/linux/libclang_rt.ubsan_standalone-x86_64.so
...
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
...
$ nm -D /usr/lib/llvm-19/lib/clang/19/lib/linux/libclang_rt.ubsan_standalone-x86_64.so | c++filt | grep class_type_info
                 U typeinfo for __cxxabiv1::__class_type_info@CXXABI_1.3
                 U typeinfo for __cxxabiv1::__si_class_type_info@CXXABI_1.3
                 U typeinfo for __cxxabiv1::__vmi_class_type_info@CXXABI_1.3

This clang-19 defaults to a static UBSan runtime -- the shared runtime isn't in the default library search path, but I can switch to the shared runtime and it works:

$ clang++-19 san.cpp -O2 -fsanitize=undefined -shared-libsan -Wl,-rpath,/usr/lib/llvm-19/lib/clang/19/lib/linux && ./a.out

I see a libclang_rt.ubsan_standalone-{x86_64,aarch64}-android.so in /system/lib64. Not sure if the platform is affected by this bug or not.

rprichard avatar Mar 26 '25 01:03 rprichard

Not sure if the platform is affected by this bug or not.

I tried this in Soong:

cc_binary {
  name: "hello",
  srcs: ["hello.cpp"],

  sanitize: {
    undefined: true,
  },
}

... and I think(?) it's not enabling vptr. The object file had these args:

-fsanitize=bool,integer-divide-by-zero,object-size,return,returns-nonnull-attribute,shift-exponent,unreachable,vla-bound,memtag-heap -fsanitize-trap=all -fsanitize-minimal-runtime -fno-sanitize-trap=integer,undefined -fno-sanitize-recover=integer,undefined -fno-sanitize=implicit-integer-sign-change -fno-sanitize=unsigned-shift-base

I don't see either -fsanitize=undefined or -fsanitize=vptr. Maybe it's just enabling a subset of the UBSan checks?

The -fsanitize-minimal-runtime flag selects libclang_rt.ubsan_minimal-aarch64-android.so rather than libclang_rt.ubsan_standalone-aarch64-android.so, but I think we link against the latter (standalone) library anyway. I think vptr isn't enabled.

The binary does depend on the shared library:

$ readelf -d /x/main/out/target/product/husky/system/bin/hello 

Dynamic section at offset 0x8000 contains 27 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libclang_rt.ubsan_standalone-aarch64-android.so]
 0x0000000000000001 (NEEDED)             Shared library: [libc++.so]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so]
 0x0000000000000001 (NEEDED)             Shared library: [libdl.so]
...

rprichard avatar Mar 28 '25 22:03 rprichard

Anyway, intuitively, the fix is for libclang_rt.ubsan_standalone-aarch64-android.so to depend on libc++[_shared].so. I think we have different NDK vs platform versions of these libraries.

IIRC, the original repro steps here didn't push the UBSan shared object to the device, so instead it used /system/lib64/libclang_rt.ubsan_standalone-aarch64-android.so, so it could start mixing libc++.so and libc++_shared.so -- not sure if that would work.

rprichard avatar Mar 28 '25 23:03 rprichard

Is that supposed to be loadable by apps? I know we did that for HWASan but I don't remember UBSan being a part of it.

DanAlbert avatar Mar 31 '25 19:03 DanAlbert

I don't see the UBSan shared object in /system/etc/public.libraries.txt, so apps shouldn't be able to load it.

rprichard avatar Mar 31 '25 20:03 rprichard

Thanks, I couldn't remember the name of the file so couldn't find it with code search.

DanAlbert avatar Mar 31 '25 20:03 DanAlbert