Implement write bounds checking within libzpaq to prevent corrupt or malicious input attempting to write beyond buffer size.

This commit is contained in:
ckolivas 2026-02-13 13:36:59 +11:00
parent 843fa4168a
commit 56ac0f1a51
3 changed files with 42 additions and 11 deletions

View file

@ -503,13 +503,35 @@ struct bufRead: public libzpaq::Reader {
struct bufWrite: public libzpaq::Writer {
uchar *c_buf;
i64 *c_len;
bufWrite(uchar *buf_, i64 *n_): c_buf(buf_), c_len(n_) {}
i64 max_len;
bufWrite(uchar *buf_, i64 *n_, i64 max_ = -1LL): c_buf(buf_), c_len(n_), max_len(max_) {}
void put(int c) {
c_buf[(*c_len)++] = (uchar)c;
//c_buf[(*c_len)++] = (uchar)c;
if (*c_len < 0) return;
if (max_len >= 0 && *c_len >= max_len) {
*c_len = -1;
return;
}
c_buf[(*c_len)++] = static_cast<uchar>(c);
}
void write(const char *buf, int n) {
if (n <= 0 || *c_len < 0) return;
if (max_len < 0) {
memcpy(c_buf + *c_len, buf, n);
*c_len += n;
return;
}
i64 avail = max_len - *c_len;
if (avail <= 0) {
*c_len = -1;
return;
}
if ((i64)n > avail) {
*c_len = -1;
return;
}
memcpy(c_buf + *c_len, buf, n);
*c_len += n;
}
@ -527,16 +549,19 @@ extern "C" void zpaq_compress(uchar *c_buf, i64 *c_len, uchar *s_buf, i64 s_len,
compress (&bufR, &bufW, level);
}
extern "C" void zpaq_decompress(uchar *s_buf, i64 *d_len, uchar *c_buf, i64 c_len,
FILE *msgout, bool progress, long thread)
extern "C" int zpaq_decompress(uchar *s_buf, i64 *d_len, uchar *c_buf, i64 c_len,
FILE *msgout, bool progress, long thread,
i64 expected_len)
{
i64 total_len = c_len;
int last_pct = 100;
bufRead bufR(c_buf, &c_len, total_len, &last_pct, progress, thread, msgout);
bufWrite bufW(s_buf, d_len);
bufWrite bufW(s_buf, d_len, expected_len);
decompress(&bufR, &bufW);
if (*d_len < 0) return -1; // overflow attempt detected
return 0;
}
#endif // LIBZPAQ_H

View file

@ -1,5 +1,5 @@
/*
Copyright (C) 2006-2016,2022 Con Kolivas
Copyright (C) 2006-2016,2022,2026 Con Kolivas
Copyright (C) 2011 Peter Hyman
Copyright (C) 1998-2003 Andrew Tridgell
@ -44,7 +44,7 @@ bool initialise_control(rzip_control *control);
#define initialize_control(_control) initialise_control(_control)
extern void zpaq_compress(uchar *c_buf, i64 *c_len, uchar *s_buf, i64 s_len, int level,
FILE *msgout, bool progress, long thread);
extern void zpaq_decompress(uchar *s_buf, i64 *d_len, uchar *c_buf, i64 c_len,
FILE *msgout, bool progress, long thread);
extern int zpaq_decompress(uchar *s_buf, i64 *d_len, uchar *c_buf, i64 c_len,
FILE *msgout, bool progress, long thread, i64 expected_len);
#endif

View file

@ -441,7 +441,7 @@ static int zpaq_decompress_buf(rzip_control *control __UNUSED__, struct uncomp_t
{
i64 dlen = ucthread->u_len;
uchar *c_buf;
int ret = 0;
int zd_ret, ret = 0;
c_buf = ucthread->s_buf;
ucthread->s_buf = malloc(round_up_page(control, dlen));
@ -452,9 +452,15 @@ static int zpaq_decompress_buf(rzip_control *control __UNUSED__, struct uncomp_t
}
dlen = 0;
zpaq_decompress(ucthread->s_buf, &dlen, c_buf, ucthread->c_len,
control->msgout, SHOW_PROGRESS ? true: false, thread);
zd_ret = zpaq_decompress(ucthread->s_buf, &dlen, c_buf, ucthread->c_len,
control->msgout, SHOW_PROGRESS ? true: false, thread,
ucthread->u_len);
if (unlikely(zd_ret < 0)) {
print_err("Attempted to write beyond expected output size, corrupted input.\n");
ret = -1;
goto out;
}
if (unlikely(dlen != ucthread->u_len)) {
print_err("Inconsistent length after decompression. Got %ld bytes, expected %"PRId64"\n", dlen, ucthread->u_len);
ret = -1;