]> git.proxmox.com Git - mirror_zfs.git/commitdiff
Don't assert on nvlists larger than SPA_MAXBLOCKSIZE
authorAllan Jude <allan@klarasystems.com>
Tue, 25 Aug 2020 18:04:20 +0000 (14:04 -0400)
committerGitHub <noreply@github.com>
Tue, 25 Aug 2020 18:04:20 +0000 (11:04 -0700)
Originally we asserted that all reads are less than SPA_MAXBLOCKSIZE
However, nvlists are not ZFS records, and are not limited to
SPA_MAXBLOCKSIZE.

Add a new environment variable, ZFS_SENDRECV_MAX_NVLIST, to allow the
user to specify the maximum size of the nvlist that can be sent or
received.
Default value: 4 * SPA_MAXBLOCKSIZE (64 MB)

Modify libzfs send routines to return a useful error if the send stream
will generate an nvlist that is beyond the maximum size.

Modify libzfs recv routines to add an explicit error message if the
nvlist is too large, rather than abort()ing.

Move the change the assert() to only trigger on data records

Reviewed-by: Paul Dagnelie <pcd@delphix.com>
Reviewed-by: Kjeld Schouten <kjeld@schouten-lebbing.nl>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Matthew Ahrens <mahrens@delphix.com>
Signed-off-by: Allan Jude <allan@klarasystems.com>
Closes #9616

include/libzfs_impl.h
lib/libzfs/libzfs_sendrecv.c
lib/libzfs/libzfs_util.c

index 0f111679764f4fb676d58ab01528298250fefef1..26e042964739bcc407ac665644ac315f98a1f2fc 100644 (file)
@@ -71,6 +71,7 @@ struct libzfs_handle {
        char libzfs_chassis_id[256];
        boolean_t libzfs_prop_debug;
        regex_t libzfs_urire;
+       uint64_t libzfs_max_nvlist;
 };
 
 struct zfs_handle {
index f1524c384febd2a4f107e814a0864fc2999b41b8..e5af6453b0efd12959a2bf06a044bef4da86f6f6 100644 (file)
@@ -610,6 +610,18 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
        fnvlist_free(sd->snapprops);
        fnvlist_free(sd->snapholds);
 
+       /* Do not allow the size of the properties list to exceed the limit */
+       if ((fnvlist_size(nvfs) + fnvlist_size(sd->fss)) >
+           zhp->zfs_hdl->libzfs_max_nvlist) {
+               (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+                   "warning: cannot send %s@%s: the size of the list of "
+                   "snapshots and properties is too large to be received "
+                   "successfully.\n"
+                   "Select a smaller number of snapshots to send.\n"),
+                   zhp->zfs_name, sd->tosnap);
+               rv = EZFS_NOSPC;
+               goto out;
+       }
        /* add this fs to nvlist */
        (void) snprintf(guidstring, sizeof (guidstring),
            "0x%llx", (longlong_t)guid);
@@ -2015,6 +2027,21 @@ send_prelim_records(zfs_handle_t *zhp, const char *from, int fd,
                        return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP,
                            errbuf));
                }
+               /*
+                * Do not allow the size of the properties list to exceed
+                * the limit
+                */
+               if ((fnvlist_size(fss) + fnvlist_size(hdrnv)) >
+                   zhp->zfs_hdl->libzfs_max_nvlist) {
+                       (void) snprintf(errbuf, sizeof (errbuf),
+                           dgettext(TEXT_DOMAIN, "warning: cannot send '%s': "
+                           "the size of the list of snapshots and properties "
+                           "is too large to be received successfully.\n"
+                           "Select a smaller number of snapshots to send.\n"),
+                           zhp->zfs_name);
+                       return (zfs_error(zhp->zfs_hdl, EZFS_NOSPC,
+                           errbuf));
+               }
                fnvlist_add_nvlist(hdrnv, "fss", fss);
                VERIFY0(nvlist_pack(hdrnv, &packbuf, &buflen, NV_ENCODE_XDR,
                    0));
@@ -2578,8 +2605,6 @@ recv_read(libzfs_handle_t *hdl, int fd, void *buf, int ilen,
        int rv;
        int len = ilen;
 
-       assert(ilen <= SPA_MAXBLOCKSIZE);
-
        do {
                rv = read(fd, cp, len);
                cp += rv;
@@ -2613,6 +2638,11 @@ recv_read_nvlist(libzfs_handle_t *hdl, int fd, int len, nvlist_t **nvp,
        if (buf == NULL)
                return (ENOMEM);
 
+       if (len > hdl->libzfs_max_nvlist) {
+               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "nvlist too large"));
+               return (ENOMEM);
+       }
+
        err = recv_read(hdl, fd, buf, len, byteswap, zc);
        if (err != 0) {
                free(buf);
@@ -3783,6 +3813,7 @@ recv_skip(libzfs_handle_t *hdl, int fd, boolean_t byteswap)
                        }
                        payload_size =
                            DRR_WRITE_PAYLOAD_SIZE(&drr->drr_u.drr_write);
+                       assert(payload_size <= SPA_MAXBLOCKSIZE);
                        (void) recv_read(hdl, fd, buf,
                            payload_size, B_FALSE, NULL);
                        break;
index c43c6bb1fe9b07d19332239996b24830f4a0aaeb..301c8ddbab1146d344988d58890642f637007237 100644 (file)
@@ -1008,6 +1008,7 @@ libzfs_init(void)
 {
        libzfs_handle_t *hdl;
        int error;
+       char *env;
 
        error = libzfs_load_module();
        if (error) {
@@ -1055,6 +1056,15 @@ libzfs_init(void)
        if (getenv("ZFS_PROP_DEBUG") != NULL) {
                hdl->libzfs_prop_debug = B_TRUE;
        }
+       if ((env = getenv("ZFS_SENDRECV_MAX_NVLIST")) != NULL) {
+               if ((error = zfs_nicestrtonum(hdl, env,
+                   &hdl->libzfs_max_nvlist))) {
+                       errno = error;
+                       return (NULL);
+               }
+       } else {
+               hdl->libzfs_max_nvlist = (SPA_MAXBLOCKSIZE * 4);
+       }
 
        /*
         * For testing, remove some settable properties and features