Author: Chen Guanyu – Xiaomi
This article is published with the author’s permission.
Native Crash has always been a pain point for various apps. This article is very in-depth and is an internal sharing by Chen Guanyu from Xiaomi, generously shared with everyone. There is very little relevant information available online, and it is indeed rare for someone proficient in Native Crash analysis to share their knowledge. Due to the depth of knowledge, it may be very difficult to understand, and I can only grasp a little bit, but this article is definitely worth collecting and reviewing regularly.
Now let’s start with the main content.
Common Types of Native Crashes
SIGSEGV |
SEGV_MAPERR |
Address not in /proc/self/maps mapping |
SEGV_ACCERR |
No access permission |
|
SEGV_MTESERR |
MTE specific type |
|
SIGABRT |
Program aborts actively, commonly called functions like abort(), raise(), etc. |
|
SIGILL |
ILL_ILLOPC |
Illegal opcode (opcode) |
ILL_ILLOPN |
Illegal operand (operand) |
|
ILL_ILLADR |
Illegal addressing |
|
ILL_ILLTRP |
Illegal trap, such as _builtintrap() actively crashing |
|
ILL_PRVOPC |
Illegal privileged opcode (privileged opcode) |
|
ILL_PRVREG |
Illegal privileged register (privileged register) |
|
ILL_COPROC |
Co-processor error |
|
ILL_BADSTK |
Internal stack error |
|
SIGBUS |
BUS_ADRALN |
Access address misalignment |
BUS_ADRERR |
Access nonexistent physical address |
|
BUS_OBJERR |
Specific object hardware error |
|
SIGFPE |
FPE_INTDIV |
Integer division by 0 |
FPE_INTOVF |
Integer overflow |
|
FPE_FLTDIV |
Floating-point division by 0 |
|
FPE_FLTOVF |
Floating-point overflow |
|
FPE_FLTUND |
Floating-point underflow |
|
FPE_FLTRES |
Floating-point result not precise |
|
FPE_FLTINV |
Invalid floating-point operation |
|
FPE_FLTSUB |
Out of bounds |
Android Logs
When a program encounters a Native Crash error, Android’s logs will output to the log crash buffer. Therefore, we can use
adb logcat -b crash to capture the corresponding error report. However, the information that the log itself can provide is limited, merely the error stack and the register information of the current thread.
——— beginning of crash 06-07 01:53:32.465 12027 12027 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 06-07 01:53:32.465 12027 12027 F DEBUG : Revision: ‘0’ 06-07 01:53:32.466 12027 12027 F DEBUG : ABI: ‘arm64’ 06-07 01:53:32.466 12027 12027 F DEBUG : Timestamp: 2022-06-07 01:53:32.033409857+0800 06-07 01:53:32.466 12027 12027 F DEBUG : Process uptime: 0s 06-07 01:53:32.466 12027 12027 F DEBUG : Cmdline: mediaserver64 06-07 01:53:32.466 12027 12027 F DEBUG : pid: 1139, tid: 11981, name: NPDecoder >>> mediaserver64 <<< 06-07 01:53:32.466 12027 12027 F DEBUG : uid: 1013 06-07 01:53:32.466 12027 12027 F DEBUG : tagged_addr_ctrl: 0000000000000001 06-07 01:53:32.466 12027 12027 F DEBUG : signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x7c02d886f0 06-07 01:53:32.466 12027 12027 F DEBUG : x0 79748c5e568e2ddc x1 0000007ca13c3618 x2 0000000000000000 x3 0000007ca1291000 06-07 01:53:32.466 12027 12027 F DEBUG : x4 0000000001909705 x5 0000000000000000 x6 0000007c02d88808 x7 b60625655bf0252f 06-07 01:53:32.467 12027 12027 F DEBUG : x8 0000000000000080 x9 0000007ca126fed7 x10 0000000000000006 x11 0000007bfd0a81fc 06-07 01:53:32.467 12027 12027 F DEBUG : x12 9ef8a95ca9649dbe x13 e44782d5ac38720e x14 0000007bfd0a8030 x15 0000001e56307b5c 06-07 01:53:32.467 12027 12027 F DEBUG : x16 0000007c95dfdb70 x17 0000007c9844f118 x18 0000007bfaa28000 x19 b400007c13c246d0 06-07 01:53:32.467 12027 12027 F DEBUG : x20 0000007c02d88730 x21 b400007c13c67c00 x22 0000000000000415 x23 0000007c02d89000 06-07 01:53:32.467 12027 12027 F DEBUG : x24 0000000000000002 x25 b400007c13c246d0 x26 b400007c13c67c00 x27 0000007c02d89000 06-07 01:53:32.467 12027 12027 F DEBUG : x28 0000007ca13c2c28 x29 0000007c02d886f0 06-07 01:53:32.467 12027 12027 F DEBUG : lr 0000007c02d886f0 sp 0000007c02d886d0 pc 0000007c02d886f0 pst 0000000080001000 06-07 01:53:32.467 12027 12027 F DEBUG : backtrace: 06-07 01:53:32.467 12027 12027 F DEBUG : #00 pc 00000000000f86f0 [anon:stack_and_tls:11981] |
When only the log stack cannot provide a more detailed analysis, we also need part of the memory information of the program and the register information. Android’s error mechanism will correspondingly generate a tombstone file saved to /data/tombstones/tombstone_xx. For machines without root permissions, the tombstone file can be captured through adb bugreport.
Tombstone
The tombstone file saves information about the error program’s architecture, commonly arm, arm64, the time of occurrence, program name, error type, the process ID and thread ID of the erroneous program, the register information at the error site, the stack and part of the memory information near the register addresses, the program memory mapping table /proc/self/maps, FD information, and the logs output by the program at the time of the error.
ABI: ‘arm64’ 【 arm64 program 】 Timestamp: 2022-06-07 01:53:32.033409857+0800 【 timestamp of error occurrence 】 Process uptime: 0s Cmdline: mediaserver64 【 program name 】 pid: 1139, tid: 11981, name: NPDecoder >>> mediaserver64 <<< 【 process ID, thread ID 】 uid: 1013 |
Error Type
signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x7c02d886f0 【 Error type is SIGSEGV, subclass is SEGV_ACCERR, error address 0x7c02d886f0 】 SIGSEGV is also the most common type of Native Crash, most of the time we refer to it as a segmentation fault, which means that a segment fault occurred at PC=0x7c02d886f0 due to access denial. |
Register Information
x0 79748c5e568e2ddc x1 0000007ca13c3618 x2 0000000000000000 x3 0000007ca1291000 x4 0000000001909705 x5 0000000000000000 x6 0000007c02d88808 x7 b60625655bf0252f x8 0000000000000080 x9 0000007ca126fed7 x10 0000000000000006 x11 0000007bfd0a81fc x12 9ef8a95ca9649dbe x13 e44782d5ac38720e x14 0000007bfd0a8030 x15 0000001e56307b5c x16 0000007c95dfdb70 x17 0000007c9844f118 x18 0000007bfaa28000 x19 b400007c13c246d0 x20 0000007c02d88730 x21 b400007c13c67c00 x22 0000000000000415 x23 0000007c02d89000 x24 0000000000000002 x25 b400007c13c246d0 x26 b400007c13c67c00 x27 0000007c02d89000 x28 0000007ca13c2c28 x29 0000007c02d886f0 lr 0000007c02d886f0 sp 0000007c02d886d0 pc 0000007c02d886f0 pst 0000000080001000 |
Stack Information
backtrace: #00 pc 00000000000f86f0 [anon:stack_and_tls:11981] |
Memory Information
The tombstone will record memory information near the current valid address of the register, with a size of 0x100. This can be modified in the macro definition MEMORY_BYTES_TO_DUMP in
system/core/debuggerd/libdebuggerd/utility.cpp
In cases like this, the memory information of the stack can help us recover the call stack.
memory near x1 (/system/lib64/libstagefright.so): 0000007ca13c35f0 0000000000000000 0000000000000000 ……………. 0000007ca13c3600 0000000000000000 0000000000000000 ……………. 0000007ca13c3610 0000000000000000 0000007ca132326c ……..l22.|… 0000007ca13c3620 0000007ca1324008 0000007ca10552e4 .@2.|….R..|… 0000007ca13c3630 0000007ca10552e8 0000007ca10552ec .R..|….R..|… 0000007ca13c3640 0000007ca10552f4 0000007ca132402c .R..|…,@2.|… 0000007ca13c3650 0000000000000000 0000000000000000 ……………. 0000007ca13c3660 0000000000000000 0000000000000000 ……………. 0000007ca13c3670 0000000000000000 0000007ca134ea84 ……….4.|… 0000007ca13c3680 0000007ca134ecec 0000007ca10552e4 ..4.|….R..|… 0000007ca13c3690 0000007ca10552e8 0000007ca10552ec .R..|….R..|… 0000007ca13c36a0 0000007ca10552f4 0000007ca134ed10 .R..|…..4.|… 0000007ca13c36b0 0000000000000000 0000000000000000 ……………. 0000007ca13c36c0 0000000000000000 0000000000000000 ……………. 0000007ca13c36d0 0000000000000000 0000007ca135d02c ……..,.5.|… 0000007ca13c36e0 0000007ca135d4b8 0000007ca10552e4 ..5.|….R..|… memory near x29 ([anon:stack_and_tls:11981]): 0000007c02d886d0 b400007c13c246d0 0000000001909705 .F..|……….. 【SP = 0x0000007c02d886d0】 0000007c02d886e0 0000007c02d88700 6f2ab3b40fa2f8ef ….|………o 0000007c02d886f0 0000007c02d88750 0000007ca133f8e0 P…|…..3.|… 【x29 = 0x0000007c02d886f0】 0000007c02d88700 0000000000000002 0000000000000000 ……………. 0000007c02d88710 0000000000000415 0000000001909705 ……………. 0000007c02d88720 0000000000000000 0000007c02d88808 …………|… 0000007c02d88730 b400007c13c67c00 0000000000000000 .|..|……….. 0000007c02d88740 0000007c02d89000 6f2ab3b40fa2f8ef ….|………o 0000007c02d88750 0000007c02d88830 0000007ca796ee7c 0…|…|…|… 0000007c02d88760 0000007ca79f3dd8 0000007ca79edb80 .=..|…….|… 0000007c02d88770 0000007c02d89000 b400007c13c04680 ….|….F..|… 0000007c02d88780 0000000000000000 0000000000000002 ……………. 0000007c02d88790 b400007c13c67c00 0000000000000000 .|..|……….. 0000007c02d887a0 0000000000000000 b400007c13c7c100 …………|… memory near sp ([anon:stack_and_tls:11981]): 0000007c02d886b0 0000000000000018 0000007caae98c58 ……..X…|… 0000007c02d886c0 0000007c02d886f0 0000007c95de6754 ….|…Tg..|… 0000007c02d886c0: 0x0000007c02d886f0 0x0000007c95de6754 0000007c02d886d0: 0xb400007c13c246d0 0x0000000001909705 /SP 0000007c02d886e0: 0x0000007c02d88700 0x6f2ab3b40fa2f8ef 0x7c02d886f0: 0x0000007c02d88750 0x0000007ca133f8e0 0x7c02d88700: 0x0000000000000002 0x0000000000000000 0x7c02d88710: 0x0000000000000415 0x0000000001909705 0x7c02d88720: 0x0000000000000000 0x0000007c02d88808 0x7c02d88730: 0xb400007c13c67c00 0x0000000000000000 0x7c02d88740: 0x0000007c02d89000 0x6f2ab3b40fa2f8ef 0x7c02d88750: 0x0000007c02d88830 0x0000007ca796ee7c 0x7c02d88760: 0x0000007ca79f3dd8 0x0000007ca79edb80 0x7c02d88770: 0x0000007c02d89000 0xb400007c13c04680 0x7c02d88780: 0x0000000000000000 0x0000000000000002 0x7c02d88790: 0xb400007c13c67c00 0x0000000000000000 0x7c02d887a0: 0x0000000000000000 0xb400007c13c7c100 memory near pc ([anon:stack_and_tls:11981]): 0x0000007c02d886d0 0x0000000000000018 0x0000007caae98c58 ……..X…|… 0x0000007c02d886e0 0x0000007c02d886f0 0x0000007c95de6754 ….|…Tg..|… 0x0000007c02d886f0 0x0000007c02d88750 0x0000007ca133f8e0 P…|…..3.|… 0x0000007c02d88700 0x0000000000000002 0x0000000000000000 ……………. 0x0000007c02d88710 0x0000000000000415 0x0000000001909705 ……………. 0x0000007c02d88720 0x0000000000000000 0x0000007c02d88808 …………|… 0x0000007c02d88730 0xb400007c13c67c00 0x0000000000000000 .|..|……….. 0x0000007c02d88740 0x0000007c02d89000 0x6f2ab3b40fa2f8ef ….|………o 0x0000007c02d88750 0x0000007c02d88830 0x0000007ca796ee7c 0…|…|…|… 0x0000007c02d88760 0x0000007ca79f3dd8 0x0000007ca79edb80 .=..|…….|… 0x0000007c02d88770 0x0000007c02d89000 0xb400007c13c04680 ….|….F..|… 0x0000007c02d88780 0x0000000000000000 0x0000000000000002 ……………. 0x0000007c02d88790 0xb400007c13c67c00 0x0000000000000000 .|..|……….. 0x0000007c02d887a0 0x0000000000000000 0xb400007c13c7c100 …………|… |
Memory Mapping Table
memory map (1146 entries): 0000005f’fabc7000-0000005f’fabc7fff r– 0 1000 /system/bin/mediaserver64 0000005f’fabc8000-0000005f’fabc9fff r-x 1000 2000 /system/bin/mediaserver64 0000005f’fabca000-0000005f’fabcafff r– 3000 1000 /system/bin/mediaserver64 0000007b’e79a3000-0000007b’e7d93fff — 0 3f1000 0000007c’a120e000-0000007c’a128efff r– 0 81000 /system/lib64/libstagefright.so 0000007c’a128f000-0000007c’a13c0fff r-x 81000 132000 /system/lib64/libstagefright.so 0000007c’a13c1000-0000007c’a13cffff r– 1b3000 f000 /system/lib64/libstagefright.so 0000007c’a13d0000-0000007c’a13d1fff rw- 1c1000 2000 /system/lib64/libstagefright.so 0000007c’a787c000-0000007c’a78f4fff r– 0 79000 /system/lib64/libmediaplayerservice.so 0000007c’a78f5000-0000007c’a79ecfff r-x 79000 f8000 /system/lib64/libmediaplayerservice.so 0000007c’a79ed000-0000007c’a79f8fff r– 171000 c000 /system/lib64/libmediaplayerservice.so 0000007c’a79f9000-0000007c’a79f9fff rw- 17c000 1000 /system/lib64/libmediaplayerservice.so |
FD Information
open files: fd 0: /dev/null (unowned) fd 1: /dev/null (unowned) fd 2: /dev/null (unowned) fd 3: socket:[62562] (unowned) fd 4: /dev/binderfs/binder (unowned) fd 5: /dev/binderfs/hwbinder (unowned) fd 6: /sys/kernel/tracing/trace_marker (unowned) fd 7: /dev/ashmem4945d9b6-db30-413c-88c5-e50674f154c7 (unowned) fd 8: /dmabuf: (unowned) fd 9: /dev/ashmem4945d9b6-db30-413c-88c5-e50674f154c7 (unowned) fd 10: /storage/emulated/0/zapya/folder/华语音乐/IN-K&王忻辰&苏星婕 – 落日与晚风.mp3 (owned by unique_fd 0x7c13c7a498) fd 11: /dev/ashmem4945d9b6-db30-413c-88c5-e50674f154c7 (unowned) … |
Coredump
The previous tombstone file content shows that its information is very limited. When we need more memory information, coredump becomes particularly important. It can capture the corresponding memory information as configured. For more information about core, see:
https://man7.org/linux/man-pages/man5/core.5.html
AOSP Method
# build/envsetup.sh# coredump_setup - enable core dumps globally for any process that has the core-file-size limit set correctly# NOTE: You must call also coredump_enable for a specific process if its core-file-size limit is not set already.# NOTE: Core dumps are written to ramdisk; they will not survive a reboot!function coredump_setup(){echo "Getting root...";adb root;adb wait-for-device;echo "Remounting root partition read-write...";adb shell mount -w -o remount -t rootfs rootfs;sleep 1;adb wait-for-device;adb shell mkdir -p /cores;adb shell mount -t tmpfs tmpfs /cores;adb shell chmod 0777 /cores;echo "Granting SELinux permission to dump in /cores...";adb shell restorecon -R /cores;echo "Set core pattern.";adb shell 'echo /cores/core.%p > /proc/sys/kernel/core_pattern';echo "Done."}# coredump_enable - enable core dumps for the specified process# $1 = PID of process (e.g., $(pid mediaserver))# NOTE: coredump_setup must have been called as well for a core# dump to actually be generated.function coredump_enable(){local PID=$1;if [ -z "$PID" ]; thenprintf "Expecting a PID!\n";return;fi;echo "Setting core limit for $PID to infinite...";adb shell /system/bin/ulimit -P $PID -c unlimited}
Common Methods
Configure coredump parameters for system_server. Since the directory where the target process’s coredump is generated is restricted by SELinux permissions, this method of configuring coredump needs to pay attention to which directories the target process has read and write SELinux permissions, and then configure the corresponding directories.
adb wait-for-deviceadb rootadb shell mkdir /data/coresadb shell chmod 777 /data/cores#adb shell setenforce 0adb shell restorecon -R /data/coresadb shell 'echo /data/cores/core.%e.%p > /proc/sys/kernel/core_pattern'adb shell 'system/bin/ulimit -P `pidof system_server` -c unlimited'#adb shell 'echo 2 > /proc/sys/fs/suid_dumpable'
Note: Ensure that the issue is unrelated to SELinux permissions. You can disable SELinux permissions by using adb shell setenforce 0.
Configure parameters to capture coredump for com.android.settings. Since the previous configuration restores SELinux permissions for the /data/cores directory as follows:
drwxrwxrwx 2 root root u:object_r:system_data_file:s0 3452 2022-07-04 15:08 cores
We know that the app must have permission to read and write files in its own /data/data/$PACKAGE/ directory, so we can configure it as follows:
adb wait-for-deviceadb rootadb shell mkdir /data/data/com.android.settings/coresadb shell chmod 777 /data/data/com.android.settings/coresadb shell restorecon -R /data/data/com.android.settings/coresadb shell 'echo /data/data/com.android.settings/cores/core.%e.%p > /proc/sys/kernel/core_pattern'adb shell 'system/bin/ulimit -P `pidof com.android.settings` -c unlimited'#adb shell 'echo 2 > /proc/sys/fs/suid_dumpable'
When we verify this app on the machine by simulating kill -11 $ kill -11 `pidof com.android.settings` $ ls /data/data/com.android.settings/cores/core.ndroid.settings.27946 |
Parameter Description
coredump_filter process default value is 0x23, only captures: private anonymous/shared anonymous/private large pages. To capture all memory information, set adb shell ‘echo 0x27 > /proc/$PID/coredump_filter’.
/proc/$PID/coredump_filter |
bit0: private anonymous |
bit1: shared anonymous |
|
bit2: private mapping with underlying file |
|
bit3: shared mapping with underlying file |
|
bit4: ELF header |
|
bit5: private large pages |
|
bit6: shared large pages |
core_pattern controls the filename of the generated core and the output location of the core. For example:
adb shell ‘echo /data/cores/core.%p > /proc/sys/kernel/core_pattern’
/proc/sys/kernel/core_pattern |
%p: add pid |
%u: add current uid |
|
%g: add current gid |
|
%s: add the signal that caused the core |
|
%t: add the unix time when the core file was generated |
|
%h: add hostname |
|
%e: add command name |
|
%E: executable file path name, replaces slashes (’/’) with exclamation marks (’!’). |
When the program calls seteuid()/setegid() to change the effective user or group of the process, the system will not generate a core for these processes by default. At this time, you may need to adjust the suid_dumpable parameter to enter debug mode or safe mode.
/proc/sys/fs/suid_dumpable |
0: default mode |
1: debug mode |
|
2: safe mode |
File Format
Core files are also a type of ELF file, so its main format components are the same as those of ELF files.
Taking the core file explained in the case as an example, its main components are the VMA in /proc/self/maps and the registers of each thread. The register information is stored in PT_NOTE, and each VMA is stored in PT_LOAD. When the VMA is filtered out, it only has Program Header descriptions and no corresponding segment.
Offline Debugging
Note: The MINIDUMP of the MTK platform is also a type of coredump, and it saves limited memory information. Core analysis can use debugging tools like GDB, lldb, etc. How to use these debugging tools will not be introduced here one by one.
$ ~/work/debug/gdb_arm64/gdb-12.1/output/bin/aarch64-linux-gdb |
When we do not have a symbol table, loading the core-file is also possible. |
(gdb) core-file PROCESS_MINIDUMP |
When we have the corresponding symbol table, we can load the symbol table directory |
(gdb) set solib-search-path symbols/ |
(gdb) set sysroot symbols/ |
(gdb) info sharedlibrary | Displays the address range of all shared libraries |
(gdb) info registers | Displays the current frame register information of the current thread |
(gdb) info locals | Displays the local variables of the current frame |
(gdb) info thread | Displays which threads exist |
(gdb) thread 2 | Switch to thread 2 |
(gdb) bt | Displays the stack of the current thread |
(gdb) thread apply all [command] | For example, print the stack of all threads |
(gdb) thread apply all bt | Let all threads execute the same command |
(gdb) frame | Displays the current frame information |
(gdb) frame 3 | Switch to frame #3 |
(gdb) print or (gdb) p | Prints the variable |
(gdb) ptype ‘android::AHandler’ | Views the data structure of a certain class or struct |
(gdb) ptype /o ‘android::AHandler’ | Views how many bytes the data type occupies |
(gdb) set print pretty on | Formats output |
(gdb) set log on | Saves the results of gdb output |
(gdb) x /gx 0x7c02d886f0 | Reads the memory content at address 0x7c02d886f0, where the output format is as follows: |
o(octal), x(hex), d(decimal), |
u(unsigned decimal), t(binary), |
f(float), a(address), i(instruction), |
c(char), s(string), |
z(hex, zero padded on the left). |
(gdb) disassemble 0x0000007c95de6708 | or (gdb) disassemble ‘android::AMessage::setTarget’ |
Displays the function assembly information |
...
Memory Detection Mechanism
ASAN
After Android 11, AOSP master has abandoned the platform development ASAN on arm64 and switched to HWASAN. AddressSanitizer (ASAN) is a compiler-based fast detection tool used to detect memory errors.
Stack and heap buffer overflow/underflow |
Stack and heap buffer overflow/underflow |
Heap use after free |
Using freed memory |
Stack use outside scope |
Out of stack range |
Double free/wild free |
Freeing memory multiple times/wrongly freeing |
HWASAN
HWASan is only applicable to Android 10 and higher versions and can only be used on AArch64 hardware, with the same detection capability as ASAN. Support for tagged-pointers is required in Linux-4.14 and above.
When compiling the Android version, include the following environment variable: $ export SANITIZE_TARGET=hwaddress To skip a certain module, add the following content in the Android.bp file under the corresponding module: sanitize: { hwaddress: false, address: false, }, In Android.mk, add the following content: LOCAL_NOSANITIZE := hwaddress APP build supports HWASAN, then add the following content in Application.mk: APP_STL := c++_shared APP_CFLAGS := -fsanitize=hwaddress -fno-omit-frame-pointer APP_LDFLAGS := -fsanitize=hwaddress |
MTE
The ARM Memory Tagging Extension (MTE) introduced in the latest Android S operates similarly to HWASAN. The biggest difference is that HWASAN requires recompilation and instrumentation of the corresponding detection functions before all memory accesses, while MTE performs detection entirely supported by hardware.
For more content, refer to the Juejin expert – Lu Banshan:
https://juejin.cn/post/6844904111570157575
https://juejin.cn/post/7013595058125406238
The Dangers of Wild Pointers
When the object pointed to is released or reclaimed, but the pointer is not modified, causing the pointer to still point to the already reclaimed memory address, this pointer is called a Wild pointer. If the memory pointed to by this wild pointer is allocated to other pointers, and this wild pointer is still in use, the program will be difficult to predict.
#include <stdio.h>class A {public:virtual ~A() = default;virtual void foo() {printf("A:%ld\n", a);}long a;};class B {public:virtual ~B() = default;virtual void foo() {printf("B:%ld\n", b);}long b;};int main(int /*argc*/, char** /*argv[]*/) {A *a = new A();A *a_bak = a;a->a = 1000L;printf("A ptr = %p\n", a);delete a; // At this point, the pointer a has been freed, so the pointer a_bak is a wild pointerB *b = new B();printf("B ptr = %p\n", b);b->b = 2000L;b->foo(); // What will happen here?delete b;return 0;}
What will the above program output? Since B and A have the same data structure size and run in the same thread, it is highly likely that the same pointer address will be allocated just released. Therefore, this program will output that pointer b and pointer a_bak are the same.
# ./data/Tester64 A ptr = 0xb400007690205010 B ptr = 0xb400007690205010 B:2000 B:2000 |
Like the above result, if the program is rewritten below, what will happen?
#include <stdio.h>class A {public:long bad;long a;};class B {public:virtual ~B() = default;virtual void foo() {b = 2000L;printf("B:%ld\n", b);}long b;};int main(int /*argc*/, char** /*argv[]*/) {A *a = new A();A *a_bak = a;printf("A ptr = %p\n", a);delete a;B *b = new B();printf("B ptr = %p\n", b);a_bak->bad = 0x20L;b->foo(); // This will cause a segmentation fault when the program runs. delete b;return 0;}
The above program will cause a segmentation fault at line 27, as bad will corrupt the virtual function table of B, causing lines 28 and 29 to search for the addresses of the foo function and destructor function in the virtual function table, resulting in a segmentation fault.
Timestamp: 2022-07-06 14:47:50.925654058+0800 Process uptime: 0s Cmdline: ./data/Tester64 pid: 12652, tid: 12652, name: Tester64 >>> ./data/Tester64 <<< uid: 0 tagged_addr_ctrl: 0000000000000001 signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x28 Cause: null pointer dereference x0 b40000762f005010 x1 b40000762f01b000 x2 0000000000000007 x3 ffffffffffffffff x4 ffffffffffffffff x5 0000000040100401 x6 b40000762f01b006 x7 3637303030303462 x8 0000000000000020 x9 a454ef76eb4317d3 x10 0000000000004001 x11 0000000000000000 x12 0000000000000000 x13 0000000000000002 x14 0000000000000010 x15 0000000000000010 x16 000000762f5a0c58 x17 000000762f5910d4 x18 000000763745c000 x19 b40000762f005010 x20 b40000762f005010 x21 0000007fe173d378 x22 0000000000000001 x23 0000000000000000 x24 0000000000000000 x25 0000000000000000 x26 0000000000000000 x27 0000000000000000 x28 0000000000000000 x29 0000007fe173d2e0 lr 0000005ac14600c8 sp 0000007fe173d2e0 pc 0000005ac14600d0 pst 0000000060001000 backtrace: #00 pc 00000000000010d0 /data/Tester64 (main+124) #01 pc 000000000008436c /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+100) |
The program will error, which is good. If the program does not error and continues to run, then this program will become very scary because you do not know how the program will run. Like the program below, rewriting control will lead the program to run in other directions.
#include <stdio.h>class A {public:void *bad;long a;};class B {public:virtual ~B() {printf("delete B\n");};virtual void foo() {b = 2000L;printf("B:%ld\n", b);}long b;};void func1() {printf("Hello !!\n");}void func2() {printf("GoGoGo !!\n");}int main(int /*argc*/, char** /*argv[]*/) {A *a = new A();A *a_bak = a;printf("A ptr = %p\n", a);delete a;B *b = new B();printf("B ptr = %p\n", b);long *data = new long[4] {0x0L, (long)func2, (long)func1, 0x0L};a_bak->bad = data;printf("Test .. \n");b->foo();delete b;printf("Done.\n");return 0;}
# ./data/Tester64 A ptr = 0xb400007ce9605010 B ptr = 0xb400007ce9605010 Test .. Hello !! GoGoGo !! Done. |
The Dangers of Array Out of Bounds
Compared to the previous wild pointers, array out of bounds can often be detected by memory detectors such as HWASAN. Of course, wild pointer situations can also be detected. Array out of bounds often leads to a situation where the memory of a certain object’s front half is polluted while the back half of the data remains normal.
#include <stdio.h>class A {public:long a = 0x55AA;long b = 0xDEAD;};int main(int /*argc*/, char** /*argv[]*/) {long *b = new long[2] {0x0L, 0x1L};A *a = new A();printf("A:%p\n", a);printf("B:%p\n", b);b[2] = 0xDEAD;printf("B2:%p\n", &b[2]);printf("0x%lx-0x%lx\n", a->a, a->b);return 0;}
Because the data size of b is the same as that of object a, when the program starts and allocates pointer addresses, they are likely to be linked together. Therefore, the out-of-bounds behavior of b[2] will corrupt the contents of object a, often after the program runs for a long time, increasing memory fragmentation, making it unknown which object’s memory will be corrupted by the out-of-bounds operation of b[2].
# ./data/Tester64 A:0xb400007fa7c05020 B:0xb400007fa7c05010 B2:0xb400007fa7c05020 0xdead-0xdead |
Machine Code Translation
The tombstone file in this article’s case shows that the PC ran to a location not in the text segment address. Here, let’s change to a tombstone file to facilitate explanation. We can generate the corresponding ELF file by compiling it, and then use objdump to obtain the corresponding assembly.
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x40 Cause: null pointer dereference x0 0000000000000020 x1 b400007bf95f0e00 x2 63692f6863726165 x3 0000000000000008 x4 b400007bf95f0e74 x5 b400007bf8ea36f4 x6 656863732f676e69 x7 732f7269645f616d x8 db4cf552ad46f717 x9 0000000000000001 x10 0000000000004001 x11 0000000000000000 x12 0000000000000000 x13 0000000000000002 x14 0000000000000010 x15 0000000000000010 x16 000000762f5a0c58 x17 000000762f5910d4 x18 000000763745c000 x19 0000000000000020 x20 0000000000000000 x21 0000007b548e9000 x22 0000007b548e9000 x23 b400007bf946ffd0 x24 0000007b548e76c0 x25 0000007b548e9000 x26 0000007b548e7b30 x27 0000007b548e7b18 x28 0000007b548e7a10 x29 0000007b548e7410 lr 0000007a8e4e7980 sp 0000007b548e73e0 pc 0000007a8e4d0e00 pst 0000000060001000 memory near pc (/apex/com.android.appsearch/lib64/libicing.so): 0000007a8e4d0de0 a90557f6f90023f7 9100c3fda9064ff4 .#…W…O…… 0000007a8e4d0df0 f94016c8d53bd056 f81f83a8aa0003f3 V.;…@……… 0000007a8e4d0e00 350001c839408008 91181c42b0fffde2 ..@9…5….B… 0000007a8e4d0e10 2a1f03e1910023e0 910023f452808ee3 .#…..…R.#.. 0000007a8e4d0e20 910022809400d695 912b742190fffde1 ….. |