Security
Headlines
HeadlinesLatestCVEs

Headline

CVE-2022-26291: Multiple concurrency UAF bug between `zpaq_decompress_buf()` and `clear_rulist()` function · Issue #206 · ckolivas/lrzip

lrzip v0.641 was discovered to contain a multiple concurrency use-after-free between the functions zpaq_decompress_buf() and clear_rulist(). This vulnerability allows attackers to cause a Denial of Service (DoS) via a crafted Irz file.

CVE
#vulnerability#linux#dos#c++

Dear all,

Our tool report that there would be multiple concurrency use-after-free between zpaq_decompress_buf() function and clear_rulist() function, in the newest master branch 465afe8.

Brief Explanation

The related code simplified from stream.c and runzip.c are shown as follow:

// Thread T0 | // Thread T1 // in clear_rulist() in runzip.c | // in zpaq_decompress_buf() in steam.c … | … struct runzip_node *node = control->ruhead; | if (unlikely(dlen != ucthread->u_len)) { struct stream_info *sinfo = node->sinfo; | ret = -1; | } else dealloc(sinfo->ucthreads); | dealloc(c_buf); dealloc(sinfo); | out: | if (ret == -1) { | dealloc(ucthread->s_buf); | ucthread->s_buf = c_buf; | }

Both thread T0 and thread T1 operate on a shared variable ucthread (i.e., T0 dealloc the a ucthread through dealloc(sinfo->ucthreads);, and T1 use the ucthread in all statements if (unlikely(dlen != ucthread->u_len)), dealloc(ucthread->s_buf);, and ucthread->s_buf = c_buf;).
However, a use-after-free can occur if the deallocation of ucthread before the use of ucthread.
For example, the following three thread interleaving can trigger three different UAFs:

Interleaving (a)

// Thread T0 | // Thread T1 | … | struct runzip_node *node = control->ruhead; | struct stream_info *sinfo = node->sinfo; | | dealloc(sinfo->ucthreads); |

dealloc(sinfo); |

                                    | ...
                                        | if (unlikely(dlen != ucthread->u\_len)) { // UAF here
                                        |     ret = -1;
                                        | } else
                                        |     dealloc(c\_buf);
                                        | out:
                                        |     if (ret == -1) {
                                        |         dealloc(ucthread->s\_buf);
                                |         ucthread->s\_buf = c\_buf;
                                    |     }

Interleaving (b)

// Thread T0 | // Thread T1 | | … | if (unlikely(dlen != ucthread->u_len)) { | ret = -1; | } else | dealloc(c_buf); | out: | if (ret == -1) {


… | struct runzip_node *node = control->ruhead; | struct stream_info *sinfo = node->sinfo; | | dealloc(sinfo->ucthreads); |

dealloc(sinfo); |

                                        |         dealloc(ucthread->s\_buf);  // UAF occur here
                                |         ucthread->s\_buf = c\_buf;
                                    |     }

Interleaving ©

// Thread T0 | // Thread T1 | | … | if (unlikely(dlen != ucthread->u_len)) { | ret = -1; | } else | dealloc(c_buf); | out: | if (ret == -1) { | dealloc(ucthread->s_buf);


… | struct runzip_node *node = control->ruhead; | struct stream_info *sinfo = node->sinfo; | | dealloc(sinfo->ucthreads); |

dealloc(sinfo); |

                                |         ucthread->s\_buf = c\_buf; // UAF occur here
                                    |     }

Reproduce through delay injection

To reproduce those use-after-free errors, we can insert two delays (e.g., sleep(1)) into the original source code.

For example, to reproduce interleaving (a) as mentioned earlier, you can insert a delay before dealloc(sinfo->ucthreads); statement in function in steam.c, and also a delay after, as shown as follows.

// In runzip.c, insert a delay after `dealloc(sinfo->ucthreads);` static void clear_rulist(rzip_control *control) { while (control->ruhead) { struct runzip_node *node = control->ruhead; struct stream_info *sinfo = node->sinfo;

    dealloc(sinfo->ucthreads);
    sleep(1); // delay here !!!!!!!!!!
    dealloc(node->pthreads);
    dealloc(sinfo->s);
    dealloc(sinfo);
    control->ruhead = node->prev;
    dealloc(node);
}

}

// In steam.c, insert a delay after `dealloc(sinfo->ucthreads);` static int zpaq_decompress_buf(rzip_control *control __UNUSED__, struct uncomp_thread *ucthread, long thread) { … zpaq_decompress(ucthread->s_buf, &dlen, c_buf, ucthread->c_len, control->msgout, SHOW_PROGRESS ? true: false, thread); sleep(1); // delay here !!! if (unlikely(dlen != ucthread->u_len)) { print_err("Inconsistent length after decompression. Got %ld bytes, expected %lld\n", dlen, ucthread->u_len); ret = -1; } else dealloc(c_buf); … }

compile the program:

CC=gcc CXX=g++ CFLAGS="-g -O0 -fsanitize=address" CXXFLAGS="-g -O0 -fsanitize=address" ./configure --enable-static-bin make

Download the testcase (I upload the POC here, please unzip first).
POC.zip

Run with the testcase with the following command:

Then, you will see the use-after-free bug report. Here is the trace reported by ASAN:

================================================================= ==33325==ERROR: AddressSanitizer: heap-use-after-free on address 0x61d0000000e8 at pc 0x000000512b00 bp 0x7f9a30bfdb10 sp 0x7f9a30bfdb08

READ of size 8 at 0x61d0000000e8 thread T3 #0 0x512aff in zpaq_decompress_buf /workdir/lrzip/stream.c:449:6 #1 0x510381 in ucompthread /workdir/lrzip/stream.c:1554:11 #2 0x7f9a3541b6da in start_thread (/lib/x86_64-linux-gnu/libpthread.so.0+0x76da) #3 0x7f9a3479771e in clone (/lib/x86_64-linux-gnu/libc.so.6+0x12171e)

0x61d0000000e8 is located 104 bytes inside of 2400-byte region [0x61d000000080,0x61d0000009e0) freed by thread T0 here: #0 0x494e1d in free /home/brian/src/final/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:123:3 #1 0x4faa0d in clear_rulist /workdir/lrzip/runzip.c:255:3 #2 0x4f7ab2 in runzip_chunk /workdir/lrzip/runzip.c:384:2 #3 0x4f4aae in runzip_fd /workdir/lrzip/runzip.c:404:7 #4 0x4d84ce in decompress_file /workdir/lrzip/lrzip.c:845:6 #5 0x4cb98b in main /workdir/lrzip/main.c:706:4 #6 0x7f9a34697bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)

previously allocated by thread T0 here: #0 0x495212 in calloc /home/brian/src/final/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:154:3 #1 0x5003e1 in open_stream_in /workdir/lrzip/stream.c:1084:33 #2 0x4f6fa5 in runzip_chunk /workdir/lrzip/runzip.c:322:7 #3 0x4f4aae in runzip_fd /workdir/lrzip/runzip.c:404:7 #4 0x4d84ce in decompress_file /workdir/lrzip/lrzip.c:845:6 #5 0x4cb98b in main /workdir/lrzip/main.c:706:4 #6 0x7f9a34697bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)

Thread T3 created by T0 here: #0 0x47fe4a in pthread_create /home/brian/src/final/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:214:3 #1 0x4fbbd0 in create_pthread /workdir/lrzip/stream.c:125:6 #2 0x507033 in fill_buffer /workdir/lrzip/stream.c:1713:6 #3 0x504184 in read_stream /workdir/lrzip/stream.c:1800:8 #4 0x4f9c88 in unzip_literal /workdir/lrzip/runzip.c:162:16 #5 0x4f731c in runzip_chunk /workdir/lrzip/runzip.c:338:9 #6 0x4f4aae in runzip_fd /workdir/lrzip/runzip.c:404:7 #7 0x4d84ce in decompress_file /workdir/lrzip/lrzip.c:845:6 #8 0x4cb98b in main /workdir/lrzip/main.c:706:4 #9 0x7f9a34697bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)

SUMMARY: AddressSanitizer: heap-use-after-free /workdir/lrzip/stream.c:449:6 in zpaq_decompress_buf Shadow bytes around the buggy address: 0x0c3a7fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c3a7fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c3a7fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c3a7fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c3a7fff8000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x0c3a7fff8010: fd fd fd fd fd fd fd fd fd fd fd fd fd[fd]fd fd 0x0c3a7fff8020: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c3a7fff8030: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c3a7fff8040: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c3a7fff8050: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c3a7fff8060: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==33325==ABORTING

I’m not sure if these use-after-free bugs could cause serious harm. I hope you can check whether it is necessary to fix these bugs.

Thanks.

Related news

Ubuntu Security Notice USN-5840-1

Ubuntu Security Notice 5840-1 - It was discovered that Long Range ZIP incorrectly handled pointers. If a user or an automated system were tricked into opening a certain specially crafted ZIP file, an attacker could possibly use this issue to cause a denial of service. This issue only affected Ubuntu 14.04 ESM, Ubuntu 16.04 ESM, Ubuntu 18.04 LTS, and Ubuntu 20.04 LTS. It was discovered that Long Range ZIP incorrectly handled pointers. If a user or an automated system were tricked into opening a certain specially crafted ZIP file, an attacker could possibly use this issue to cause a denial of service. This issue only affected Ubuntu 18.04 LTS and Ubuntu 20.04 LTS.

CVE: Latest News

CVE-2023-50976: Transactions API Authorization by oleiman · Pull Request #14969 · redpanda-data/redpanda
CVE-2023-6905
CVE-2023-6903
CVE-2023-6904
CVE-2023-3907