Skip to content

Commit 73968de

Browse files
authored
Reject streams that set ->drr_payloadlen to unreasonably large values
In the zstream code, Coverity reported: "The argument could be controlled by an attacker, who could invoke the function with arbitrary values (for example, a very high or negative buffer size)." It did not report this in the kernel. This is likely because the userspace code stored this in an int before passing it into the allocator, while the kernel code stored it in a uint32_t. However, this did reveal a potentially real problem. On 32-bit systems and systems with only 4GB of physical memory or less in general, it is possible to pass a large enough value that the system will hang. Even worse, on Linux systems, the kernel memory allocator is not able to support allocations up to the maximum 4GB allocation size that this allows. This had already been limited in userspace to 64MB by `ZFS_SENDRECV_MAX_NVLIST`, but we need a hard limit in the kernel to protect systems. After some discussion, we settle on 256MB as a hard upper limit. Attempting to receive a stream that requires more memory than that will result in E2BIG being returned to user space. Reported-by: Coverity (CID-1529836) Reported-by: Coverity (CID-1529837) Reported-by: Coverity (CID-1529838) Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Richard Yao <[email protected]> Closes #14285
1 parent 69f024a commit 73968de

File tree

5 files changed

+34
-6
lines changed

5 files changed

+34
-6
lines changed

cmd/zstream/zstream_decompress.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,10 @@ zstream_do_decompress(int argc, char *argv[])
179179
VERIFY0(begin++);
180180
seen = B_TRUE;
181181

182-
int sz = drr->drr_payloadlen;
182+
uint32_t sz = drr->drr_payloadlen;
183+
184+
VERIFY3U(sz, <=, 1U << 28);
185+
183186
if (sz != 0) {
184187
if (sz > bufsz) {
185188
buf = realloc(buf, sz);

cmd/zstream/zstream_recompress.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,10 @@ zstream_do_recompress(int argc, char *argv[])
160160
VERIFY0(begin++);
161161
seen = B_TRUE;
162162

163-
int sz = drr->drr_payloadlen;
163+
uint32_t sz = drr->drr_payloadlen;
164+
165+
VERIFY3U(sz, <=, 1U << 28);
166+
164167
if (sz != 0) {
165168
if (sz > bufsz) {
166169
buf = realloc(buf, sz);

cmd/zstream/zstream_redup.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,10 @@ zfs_redup_stream(int infd, int outfd, boolean_t verbose)
254254
/* cppcheck-suppress syntaxError */
255255
DMU_SET_FEATUREFLAGS(drrb->drr_versioninfo, fflags);
256256

257-
int sz = drr->drr_payloadlen;
257+
uint32_t sz = drr->drr_payloadlen;
258+
259+
VERIFY3U(sz, <=, 1U << 28);
260+
258261
if (sz != 0) {
259262
if (sz > bufsz) {
260263
free(buf);

lib/libzfs/libzfs_sendrecv.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5197,6 +5197,14 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
51975197
destsnap);
51985198
*cp = '@';
51995199
break;
5200+
case E2BIG:
5201+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
5202+
"zfs receive required kernel memory allocation "
5203+
"larger than the system can support. Please file "
5204+
"an issue at the OpenZFS issue tracker:\n"
5205+
"https://github.com/openzfs/zfs/issues/new"));
5206+
(void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
5207+
break;
52005208
case EBUSY:
52015209
if (hastoken) {
52025210
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,

module/zfs/dmu_recv.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
* Copyright (c) 2022 Axcient.
3232
*/
3333

34+
#include <sys/arc.h>
3435
#include <sys/spa_impl.h>
3536
#include <sys/dmu.h>
3637
#include <sys/dmu_impl.h>
@@ -1246,19 +1247,29 @@ dmu_recv_begin(char *tofs, char *tosnap, dmu_replay_record_t *drr_begin,
12461247

12471248
uint32_t payloadlen = drc->drc_drr_begin->drr_payloadlen;
12481249
void *payload = NULL;
1250+
1251+
/*
1252+
* Since OpenZFS 2.0.0, we have enforced a 64MB limit in userspace
1253+
* configurable via ZFS_SENDRECV_MAX_NVLIST. We enforce 256MB as a hard
1254+
* upper limit. Systems with less than 1GB of RAM will see a lower
1255+
* limit from `arc_all_memory() / 4`.
1256+
*/
1257+
if (payloadlen > (MIN((1U << 28), arc_all_memory() / 4)))
1258+
return (E2BIG);
1259+
12491260
if (payloadlen != 0)
1250-
payload = kmem_alloc(payloadlen, KM_SLEEP);
1261+
payload = vmem_alloc(payloadlen, KM_SLEEP);
12511262

12521263
err = receive_read_payload_and_next_header(drc, payloadlen,
12531264
payload);
12541265
if (err != 0) {
1255-
kmem_free(payload, payloadlen);
1266+
vmem_free(payload, payloadlen);
12561267
return (err);
12571268
}
12581269
if (payloadlen != 0) {
12591270
err = nvlist_unpack(payload, payloadlen, &drc->drc_begin_nvl,
12601271
KM_SLEEP);
1261-
kmem_free(payload, payloadlen);
1272+
vmem_free(payload, payloadlen);
12621273
if (err != 0) {
12631274
kmem_free(drc->drc_next_rrd,
12641275
sizeof (*drc->drc_next_rrd));

0 commit comments

Comments
 (0)