Commit 1808f8cc authored by Mike Marshall's avatar Mike Marshall
Browse files

Orangefs: add verification to decode_dirents



Also add comments to decode_dirents and make it more readable.

Signed-off-by: default avatarMike Marshall <hubcap@omnibond.com>
parent fcac9d57
Loading
Loading
Loading
Loading
+93 −25
Original line number Diff line number Diff line
@@ -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;
		}

		gossip_debug(GOSSIP_DIR_DEBUG,
			     "%s: size:%zu: len:%d:\n",
			     __func__,
			     size,
			     len);

		readdir->dirent_array[i].d_name = buf + 4;
		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,