diff options
Diffstat (limited to 'fs/orangefs')
-rw-r--r-- | fs/orangefs/dir.c | 118 |
1 files changed, 93 insertions, 25 deletions
diff --git a/fs/orangefs/dir.c b/fs/orangefs/dir.c index 58558e37fb8a..6f5836d6a7a3 100644 --- a/fs/orangefs/dir.c +++ b/fs/orangefs/dir.c @@ -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, |