]> git.proxmox.com Git - mirror_zfs.git/blobdiff - module/zcommon/zfs_namecheck.c
OpenZFS 9330 - stack overflow when creating a deeply nested dataset
[mirror_zfs.git] / module / zcommon / zfs_namecheck.c
index aefde90876ae98a872987e16233df223220c3458..58b23b0e00b0b11a2301afb14221fffa948e3669 100644 (file)
@@ -23,7 +23,7 @@
  * Use is subject to license terms.
  */
 /*
- * Copyright (c) 2013 by Delphix. All rights reserved.
+ * Copyright (c) 2013, 2016 by Delphix. All rights reserved.
  */
 
 /*
@@ -34,8 +34,6 @@
  * name is invalid.  In the kernel, we only care whether it's valid or not.
  * Each routine therefore takes a 'namecheck_err_t' which describes exactly why
  * the name failed to validate.
- *
- * Each function returns 0 on success, -1 on error.
  */
 
 #if !defined(_KERNEL)
 #include "zfs_namecheck.h"
 #include "zfs_deleg.h"
 
+/*
+ * Deeply nested datasets can overflow the stack, so we put a limit
+ * in the amount of nesting a path can have. zfs_max_dataset_nesting
+ * can be tuned temporarily to fix existing datasets that exceed our
+ * predefined limit.
+ */
+int zfs_max_dataset_nesting = 50;
+
 static int
 valid_char(char c)
 {
@@ -57,11 +63,36 @@ valid_char(char c)
            c == '-' || c == '_' || c == '.' || c == ':' || c == ' ');
 }
 
+/*
+ * Looks at a path and returns its level of nesting (depth).
+ */
+int
+get_dataset_depth(const char *path)
+{
+       const char *loc = path;
+       int nesting = 0;
+
+       /*
+        * Keep track of nesting until you hit the end of the
+        * path or found the snapshot/bookmark seperator.
+        */
+       for (int i = 0; loc[i] != '\0' &&
+           loc[i] != '@' &&
+           loc[i] != '#'; i++) {
+               if (loc[i] == '/')
+                       nesting++;
+       }
+
+       return (nesting);
+}
+
 /*
  * Snapshot names must be made up of alphanumeric characters plus the following
  * characters:
  *
- *     [-_.: ]
+ *     [-_.: ]
+ *
+ * Returns 0 on success, -1 on error.
  */
 int
 zfs_component_namecheck(const char *path, namecheck_err_t *why, char *what)
@@ -97,6 +128,8 @@ zfs_component_namecheck(const char *path, namecheck_err_t *why, char *what)
  * Permissions set name must start with the letter '@' followed by the
  * same character restrictions as snapshot names, except that the name
  * cannot exceed 64 characters.
+ *
+ * Returns 0 on success, -1 on error.
  */
 int
 permset_namecheck(const char *path, namecheck_err_t *why, char *what)
@@ -118,29 +151,41 @@ permset_namecheck(const char *path, namecheck_err_t *why, char *what)
        return (zfs_component_namecheck(&path[1], why, what));
 }
 
+/*
+ * Dataset paths should not be deeper than zfs_max_dataset_nesting
+ * in terms of nesting.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+int
+dataset_nestcheck(const char *path)
+{
+       return ((get_dataset_depth(path) < zfs_max_dataset_nesting) ? 0 : -1);
+}
+
 /*
  * Entity names must be of the following form:
  *
- *     [component/]*[component][(@|#)component]?
+ *     [component/]*[component][(@|#)component]?
  *
  * Where each component is made up of alphanumeric characters plus the following
  * characters:
  *
- *     [-_.:%]
+ *     [-_.:%]
  *
  * We allow '%' here as we use that character internally to create unique
  * names for temporary clones (for online recv).
+ *
+ * Returns 0 on success, -1 on error.
  */
 int
 entity_namecheck(const char *path, namecheck_err_t *why, char *what)
 {
-       const char *start, *end;
-       int found_delim;
+       const char *end;
 
        /*
         * Make sure the name is not too long.
         */
-
        if (strlen(path) >= ZFS_MAX_DATASET_NAME_LEN) {
                if (why)
                        *why = NAME_ERR_TOOLONG;
@@ -160,8 +205,8 @@ entity_namecheck(const char *path, namecheck_err_t *why, char *what)
                return (-1);
        }
 
-       start = path;
-       found_delim = 0;
+       const char *start = path;
+       boolean_t found_delim = B_FALSE;
        for (;;) {
                /* Find the end of this component */
                end = start;
@@ -196,7 +241,7 @@ entity_namecheck(const char *path, namecheck_err_t *why, char *what)
                                return (-1);
                        }
 
-                       found_delim = 1;
+                       found_delim = B_TRUE;
                }
 
                /* Zero-length components are not allowed */
@@ -248,6 +293,8 @@ dataset_namecheck(const char *path, namecheck_err_t *why, char *what)
  * mountpoint names must be of the following form:
  *
  *     /[component][/]*[component][/]
+ *
+ * Returns 0 on success, -1 on error.
  */
 int
 mountpoint_namecheck(const char *path, namecheck_err_t *why)
@@ -292,6 +339,8 @@ mountpoint_namecheck(const char *path, namecheck_err_t *why)
  * dataset names, with the additional restriction that the pool name must begin
  * with a letter.  The pool names 'raidz' and 'mirror' are also reserved names
  * that cannot be used.
+ *
+ * Returns 0 on success, -1 on error.
  */
 int
 pool_namecheck(const char *pool, namecheck_err_t *why, char *what)
@@ -351,4 +400,10 @@ pool_namecheck(const char *pool, namecheck_err_t *why, char *what)
 EXPORT_SYMBOL(pool_namecheck);
 EXPORT_SYMBOL(dataset_namecheck);
 EXPORT_SYMBOL(zfs_component_namecheck);
+EXPORT_SYMBOL(dataset_nestcheck);
+EXPORT_SYMBOL(get_dataset_depth);
+EXPORT_SYMBOL(zfs_max_dataset_nesting);
+
+module_param(zfs_max_dataset_nesting, int, 0644);
+MODULE_PARM_DESC(zfs_max_dataset_nesting, "Maximum depth of nested datasets");
 #endif