Buffer overflow in MUPIP SIZE causes stack corruption in rare cases
Final Release Note
NO RELEASE NOTE NEEDED as this is not an issue in any YottaDB release
Description
This is an issue first recorded at !935 (comment 541449303).
In-house testing after !935 (merged) (i.e. GT.M V6.3-010
) was merged showed a rare failure where MUPIP SIZE
failed with a SIGABRT signal (SIG-6) due to stack corruption. Below is an example C-stack at the time of the failure. Frame 13 call to __stack_chk_fail
indicates the corruption.
#0 __pthread_kill (threadid=<optimized out>, signo=3) at ../sysdeps/unix/sysv/linux/pthread_kill.c:54
#1 gtm_dump_core () at sr_unix/gtm_dump_core.c:74
#2 gtm_fork_n_core () at sr_unix/gtm_fork_n_core.c:163
#3 ch_cond_core () at sr_unix/ch_cond_core.c:80
#4 rts_error_va (csa=0x0, argcnt=7, var=...) at sr_unix/rts_error.c:192
#5 rts_error_csa (csa=0x0, argcnt=7) at sr_unix/rts_error.c:99
#6 generic_signal_handler (sig=6, info=..., context=..., is_os_signal_handler=1) at sr_unix/generic_signal_handler.c:445
#7 ydb_os_signal_handler (sig=6, info=..., context=...) at sr_unix/ydb_os_signal_handler.c:85
#8 <signal handler called>
#9 raise () from /usr/lib/x86_64-linux-gnu/libc.so.6
#10 abort () from /usr/lib/x86_64-linux-gnu/libc.so.6
#11 ?? () from /usr/lib/x86_64-linux-gnu/libc.so.6
#12 __fortify_fail () from /usr/lib/x86_64-linux-gnu/libc.so.6
#13 __stack_chk_fail () from /usr/lib/x86_64-linux-gnu/libc.so.6
#14 dfs (lvl=1, pBlkBase=... "\001", endtree=1, skiprecs=1) at sr_unix/mu_size_scan.c:358
#15 mu_size_scan (gl_ptr=..., level=1) at sr_unix/mu_size_scan.c:187
#16 mupip_size () at sr_unix/mupip_size.c:244
#17 mupip_main (argc=3, argv=..., envp=...) at sr_unix/mupip_main.c:122
#18 dlopen_libyottadb (argc=3, argv=..., envp=..., main_func=..."mupip_main") at sr_unix/dlopen_libyottadb.c:151
#19 main (argc=3, argv=..., envp=...) at sr_unix/mupip.c:22
The issue is the memcpy()
at line 53 below.
sr_unix/mupip_size.h
--------------------
37 #define GET_KEY_CPY_BUFF(KEY_BASE, REC_CMPC, PTR, FIRST_KEY, NAME_LEN, KEY_SIZE, BUFF, BUFF_LENGTH, REC_LEN) \
38 MBSTART { \
39 for (PTR = key_base; ;) \
40 { \
41 if (KEY_DELIMITER == *PTR++) \
42 { \
43 if (FIRST_KEY) \
44 { \
45 FIRST_KEY = FALSE; \
46 NAME_LEN = (int)(PTR - key_base); \
47 } \
48 if (KEY_DELIMITER == *PTR++) \
49 break; \
50 } \
51 } \
52 KEY_SIZE = (int)(PTR - KEY_BASE); \
--> 53 memcpy(BUFF + REC_CMPC, KEY_BASE, KEY_SIZE); \
54 BUFF_LENGTH = REC_CMPC + KEY_SIZE; \
55 REC_LEN = BUFF_LENGTH; \
56 } MBEND
It has no buffer overflow checks for BUFF
. Both callers of this macro allocate BUFF
on the C-stack with a size of MAX_KEY_SZ + 1
which translates to around 1025 bytes. We skip the first REC_CMPC
bytes and copy KEY_SIZE
bytes. This assumes that REC_CMPC + KEY_SIZE
is within MAX_KEY_SZ + 1
bytes. But is not guaranteed as REC_CMP
and KEY_SIZE
are both determined based on the buffer contents which could be concurrently changing.
We need to skip the memcpy
(and signal a restart) if REC_CMPC + KEY_SIZE
ends up being greater than SIZEOF(BUFF)
.
Draft Release Note
mupip size
works correctly even in a highly concurrent update scenario. Previously it was possible for it to terminate abnormally with a SIGABRT
(i.e. SIG-6
) signal due to a buffer overflow that caused stack corruption. This was noticed after integrating the upstream code changes from GT.M V6.3-010.