Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

pNFS: Fix uninited ptr deref in block/scsi layout

The error occurs on the third attempt to encode extents. When function
ext_tree_prepare_commit() reallocates a larger buffer to retry encoding
extents, the "layoutupdate_pages" page array is initialized only after the
retry loop. But ext_tree_free_commitdata() is called on every iteration
and tries to put pages in the array, thus dereferencing uninitialized
pointers.

An additional problem is that there is no limit on the maximum possible
buffer_size. When there are too many extents, the client may create a
layoutcommit that is larger than the maximum possible RPC size accepted
by the server.

During testing, we observed two typical scenarios. First, one memory page
for extents is enough when we work with small files, append data to the
end of the file, or preallocate extents before writing. But when we fill
a new large file without preallocating, the number of extents can be huge,
and counting the number of written extents in ext_tree_encode_commit()
does not help much. Since this number increases even more between
unlocking and locking of ext_tree, the reallocated buffer may not be
large enough again and again.

Co-developed-by: Konstantin Evtushenko <koevtushenko@yandex.com>
Signed-off-by: Konstantin Evtushenko <koevtushenko@yandex.com>
Signed-off-by: Sergey Bashirov <sergeybashirov@gmail.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Link: https://lore.kernel.org/r/20250630183537.196479-2-sergeybashirov@gmail.com
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>

authored by

Sergey Bashirov and committed by
Trond Myklebust
9768797c 3b3bc9a1

+15 -5
+15 -5
fs/nfs/blocklayout/extent_tree.c
··· 552 552 return ret; 553 553 } 554 554 555 + /** 556 + * ext_tree_prepare_commit - encode extents that need to be committed 557 + * @arg: layout commit data 558 + * 559 + * Return values: 560 + * %0: Success, all required extents are encoded 561 + * %-ENOSPC: Some extents are encoded, but not all, due to RPC size limit 562 + * %-ENOMEM: Out of memory, extents not encoded 563 + */ 555 564 int 556 565 ext_tree_prepare_commit(struct nfs4_layoutcommit_args *arg) 557 566 { ··· 577 568 start_p = page_address(arg->layoutupdate_page); 578 569 arg->layoutupdate_pages = &arg->layoutupdate_page; 579 570 580 - retry: 581 - ret = ext_tree_encode_commit(bl, start_p + 1, buffer_size, &count, &arg->lastbytewritten); 571 + ret = ext_tree_encode_commit(bl, start_p + 1, buffer_size, 572 + &count, &arg->lastbytewritten); 582 573 if (unlikely(ret)) { 583 574 ext_tree_free_commitdata(arg, buffer_size); 584 575 585 - buffer_size = ext_tree_layoutupdate_size(bl, count); 576 + buffer_size = NFS_SERVER(arg->inode)->wsize; 586 577 count = 0; 587 578 588 579 arg->layoutupdate_pages = ··· 597 588 return -ENOMEM; 598 589 } 599 590 600 - goto retry; 591 + ret = ext_tree_encode_commit(bl, start_p + 1, buffer_size, 592 + &count, &arg->lastbytewritten); 601 593 } 602 594 603 595 *start_p = cpu_to_be32(count); ··· 618 608 } 619 609 620 610 dprintk("%s found %zu ranges\n", __func__, count); 621 - return 0; 611 + return ret; 622 612 } 623 613 624 614 void