Orangefs: add verification to decode_dirents
authorMike Marshall <hubcap@omnibond.com>
Fri, 15 Jan 2016 18:10:52 +0000 (13:10 -0500)
committerMike Marshall <hubcap@omnibond.com>
Fri, 15 Jan 2016 18:10:52 +0000 (13:10 -0500)
Also add comments to decode_dirents and make it more readable.

Signed-off-by: Mike Marshall <hubcap@omnibond.com>
fs/orangefs/dir.c

index 58558e3..6f5836d 100644 (file)
@@ -15,7 +15,14 @@ struct readdir_handle_s {
 };
 
 /*
- * decode routine needed by kmod to make sense of the shared page for readdirs.
+ * decode routine used by kmod to deal with the blob sent from
+ * userspace for readdirs. The blob contains zero or more of these
+ * sub-blobs:
+ *   __u32 - represents length of the character string that follows.
+ *   string - between 1 and ORANGEFS_NAME_MAX bytes long.
+ *   padding - (if needed) to cause the __u32 plus the string to be
+ *             eight byte aligned.
+ *   khandle - sizeof(khandle) bytes.
  */
 static long decode_dirents(char *ptr, size_t size,
                            struct orangefs_readdir_response_s *readdir)
@@ -24,54 +31,115 @@ static long decode_dirents(char *ptr, size_t size,
        struct orangefs_readdir_response_s *rd =
                (struct orangefs_readdir_response_s *) ptr;
        char *buf = ptr;
+       int khandle_size = sizeof(struct orangefs_khandle);
+       size_t offset = offsetof(struct orangefs_readdir_response_s,
+                               dirent_array);
+       /* 8 reflects eight byte alignment */
+       int smallest_blob = khandle_size + 8;
+       __u32 len;
+       int aligned_len;
+       int sizeof_u32 = sizeof(__u32);
+       long ret;
 
-       if (size < offsetof(struct orangefs_readdir_response_s, dirent_array))
-               return -EINVAL;
+       gossip_debug(GOSSIP_DIR_DEBUG, "%s: size:%zu:\n", __func__, size);
+
+       /* size is = offset on empty dirs, > offset on non-empty dirs... */
+       if (size < offset) {
+               gossip_err("%s: size:%zu: offset:%zu:\n",
+                          __func__,
+                          size,
+                          offset);
+               ret = -EINVAL;
+               goto out;
+       }
+
+        if ((size == offset) && (readdir->orangefs_dirent_outcount != 0)) {
+               gossip_err("%s: size:%zu: dirent_outcount:%d:\n",
+                          __func__,
+                          size,
+                          readdir->orangefs_dirent_outcount);
+               ret = -EINVAL;
+               goto out;
+       }
 
        readdir->token = rd->token;
        readdir->orangefs_dirent_outcount = rd->orangefs_dirent_outcount;
        readdir->dirent_array = kcalloc(readdir->orangefs_dirent_outcount,
                                        sizeof(*readdir->dirent_array),
                                        GFP_KERNEL);
-       if (readdir->dirent_array == NULL)
-               return -ENOMEM;
+       if (readdir->dirent_array == NULL) {
+               gossip_err("%s: kcalloc failed.\n", __func__);
+               ret = -ENOMEM;
+               goto out;
+       }
 
-       buf += offsetof(struct orangefs_readdir_response_s, dirent_array);
-       size -= offsetof(struct orangefs_readdir_response_s, dirent_array);
+       buf += offset;
+       size -= offset;
 
        for (i = 0; i < readdir->orangefs_dirent_outcount; i++) {
-               __u32 len;
-
-               if (size < 4)
-                       goto Einval;
+               if (size < smallest_blob) {
+                       gossip_err("%s: size:%zu: smallest_blob:%d:\n",
+                                  __func__,
+                                  size,
+                                  smallest_blob);
+                       ret = -EINVAL;
+                       goto free;
+               }
 
                len = *(__u32 *)buf;
-               if (len >= (unsigned)-24)
-                       goto Einval;
+               if ((len < 1) || (len > ORANGEFS_NAME_MAX)) {
+                       gossip_err("%s: len:%d:\n", __func__, len);
+                       ret = -EINVAL;
+                       goto free;
+               }
 
-               readdir->dirent_array[i].d_name = buf + 4;
+               gossip_debug(GOSSIP_DIR_DEBUG,
+                            "%s: size:%zu: len:%d:\n",
+                            __func__,
+                            size,
+                            len);
+
+               readdir->dirent_array[i].d_name = buf + sizeof_u32;
                readdir->dirent_array[i].d_length = len;
 
                /*
-                * Round 4 + len + 1, which is the encoded size plus the string
-                * plus the null terminator to the nearest eight byte boundry.
+                * Calculate "aligned" length of this string and its
+                * associated __u32 descriptor.
                 */
-               len = ((4 + len + 1) + 7) & ~7;
-               if (size < len + 16)
-                       goto Einval;
-               size -= len + 16;
+               aligned_len = ((sizeof_u32 + len + 1) + 7) & ~7;
+               gossip_debug(GOSSIP_DIR_DEBUG,
+                            "%s: aligned_len:%d:\n",
+                            __func__,
+                            aligned_len);
 
-               buf += len;
+               /*
+                * The end of the blob should coincide with the end
+                * of the last sub-blob.
+                */
+               if (size < aligned_len + khandle_size) {
+                       gossip_err("%s: ran off the end of the blob.\n",
+                                  __func__);
+                       ret = -EINVAL;
+                       goto free;
+               }
+               size -= aligned_len + khandle_size;
+
+               buf += aligned_len;
 
                readdir->dirent_array[i].khandle =
                        *(struct orangefs_khandle *) buf;
-               buf += 16;
+               buf += khandle_size;
        }
-       return buf - ptr;
-Einval:
+       ret = buf - ptr;
+       gossip_debug(GOSSIP_DIR_DEBUG, "%s: returning:%ld:\n", __func__, ret);
+       goto out;
+
+free:
        kfree(readdir->dirent_array);
        readdir->dirent_array = NULL;
-       return -EINVAL;
+
+out:
+       return ret;
 }
 
 static long readdir_handle_ctor(struct readdir_handle_s *rhandle, void *buf,