lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Tue, 25 Oct 2022 11:57:05 +0300
From:   Andy Shevchenko <andriy.shevchenko@...el.com>
To:     Kees Cook <keescook@...omium.org>
Cc:     "Darrick J . Wong" <djwong@...nel.org>,
        "Gustavo A. R. Silva" <gustavoars@...nel.org>,
        Keith Packard <keithp@...thp.com>,
        Francis Laniel <laniel_francis@...vacyrequired.com>,
        Daniel Axtens <dja@...ens.net>,
        Dan Williams <dan.j.williams@...el.com>,
        Vincenzo Frascino <vincenzo.frascino@....com>,
        Guenter Roeck <linux@...ck-us.net>,
        Daniel Vetter <daniel.vetter@...ll.ch>,
        Tadeusz Struk <tadeusz.struk@...aro.org>,
        Zorro Lang <zlang@...hat.com>,
        Geert Uytterhoeven <geert@...ux-m68k.org>,
        Paolo Abeni <pabeni@...hat.com>, linux-kernel@...r.kernel.org,
        linux-xfs@...r.kernel.org, linux-hardening@...r.kernel.org
Subject: Re: [PATCH v2 1/2] Introduce flexible array struct helpers

On Mon, Oct 24, 2022 at 10:20:57AM -0700, Kees Cook wrote:
> The compiler is not able to automatically perform bounds checking on
> structures that end in flexible arrays: __builtin_object_size() is
> compile-time only, and __builtin_dynamic_object_size() may not always
> be able to figure it out. Any possible run-time checks are currently
> short-circuited (or trigger false positives) because there isn't
> an obvious common way to figure out the bounds of such a structure.
> C has no way (yet[1]) to signify which struct member holds the number
> of allocated flexible array elements (like exists in other languages).
> 
> As a result, the kernel (and C projects generally) need to manually
> check the bounds, check the element size calculations, and perform sanity
> checking on all the associated variable types in between (e.g. 260
> cannot be stored in a u8). This is extremely fragile.
> 
> However, even if we could do all this through a magic memcpy(), the API
> itself doesn't provide meaningful feedback, which forces the kernel
> into an "all or nothing" approach: either do the copy or panic the
> system. Any failure conditions should be _detectable_, with API users
> able to gracefully recover.
> 
> To deal with these needs, create the first of a set of helper functions
> that do the work of memcpy() but perform the needed bounds checking
> based on the arguments given: flex_cpy().
> 
> This API will be expanded in the future to also include the common
> pattern of "allocate and copy": flex_dup(), "deserialize and copy":
> mem_to_flex(), and "deserialize, allocate, and copy": mem_to_flex_dup().
> 
> The concept of a "flexible array structure" is introduced, which is a
> struct that has both a trailing flexible array member _and_ an element
> count member. If a struct lacks the element count member, it's just a
> blob: there are no bounds associated with it.
> 
> The most common style of flexible array struct in the kernel is a
> "simple" one, where both the flex-array and element-count are present:
> 
>     struct flex_array_struct_example {
>         ...		/* arbitrary members */
>         u16 part_count;	/* count of elements stored in "parts" below. */
>         ...		/* arbitrary members */
>         u32 parts[];	/* flexible array with elements of type u32. */
>     };
> 
> Next are "encapsulating flexible array structs", which is just a struct
> that contains a flexible array struct as its final member:
> 
>     struct encapsulating_example {
>         ...		/* arbitrary members */
>         struct flex_array_struct_example fas;
>     };
> 
> There are also "split" flex array structs, which have the element-count
> member in a separate struct level than the flex-array member:
> 
>     struct split_example {
>         ...		/* arbitrary members */
>         u16 part_count;	/* count of elements stored in "parts" below. */
>         ...		/* arbitrary members */
>         struct blob_example {
>             ...		/* other blob members */
>             u32 parts[];/* flexible array with elements of type u32. */
>         } blob;
>     };
> 
> To have the helpers deal with these arbitrary layouts, the names of the
> flex-array and element-count members need to be specified with each use
> (since C lacks the array-with-length syntax[1] so the compiler cannot
> automatically determine them). However, for the "simple" (most common)
> case, we can get close to "automatic" by explicitly declaring common
> member aliases "__flex_array_elements", and "__flex_array_elements_count"
> respectively. The regular helpers use these members, but extended helpers
> exist to cover the other two code patterns.
> 
> For example, using the newly introduced flex_cpy():
> 
>     /* Flexible array struct with members identified. */
>     struct simple {
>         int mode;
>         DECLARE_FAS_COUNT(int, how_many);
>         unsigned long flags;
>         DECLARE_FAS_ARRAY(u32, value);
>     };
>     ...
> 
>     int do_simple(struct simple *src) {
>         struct simple *instance = NULL;
>         size_t bytes;
>         int rc;
> 
>         /* Allocate */
>         if (fas_bytes(src, &bytes))
>             return -E2BIG;
>         instance = kmalloc(bytes, GFP_KERNEL);
>         if (!instance)
>             return -ENOMEM;
>         instance->how_many = src->how_many;
>         /* Copy */
>         rc = flex_cpy(instance, src);
>         if (rc) {
>             kfree(instance);
>             return rc;
>         }
>         return 0;
>     }
> 
> If anything goes wrong, it returns a negative errno. Note that the
> "allocate" pattern above will be the basis of the future flex_dup()
> helper.
> 
> With these helpers the kernel can move away from many of the open-coded
> patterns of using memcpy() with a dynamically-sized destination buffer.
> 
> [1] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1990.htm
> 
> Cc: "Gustavo A. R. Silva" <gustavoars@...nel.org>
> Cc: Keith Packard <keithp@...thp.com>
> Cc: Francis Laniel <laniel_francis@...vacyrequired.com>
> Cc: Daniel Axtens <dja@...ens.net>
> Cc: Dan Williams <dan.j.williams@...el.com>
> Cc: Vincenzo Frascino <vincenzo.frascino@....com>
> Cc: Guenter Roeck <linux@...ck-us.net>
> Cc: Daniel Vetter <daniel.vetter@...ll.ch>
> Cc: Tadeusz Struk <tadeusz.struk@...aro.org>
> Signed-off-by: Kees Cook <keescook@...omium.org>
> Link: https://lore.kernel.org/r/20220504014440.3697851-3-keescook@chromium.org
> ---
>  include/linux/flex_array.h  | 325 ++++++++++++++++++++++++++++++++++++
>  include/linux/string.h      |   1 +
>  include/uapi/linux/stddef.h |  14 ++
>  3 files changed, 340 insertions(+)
>  create mode 100644 include/linux/flex_array.h
> 
> diff --git a/include/linux/flex_array.h b/include/linux/flex_array.h
> new file mode 100644
> index 000000000000..ebdbf0e5f722
> --- /dev/null
> +++ b/include/linux/flex_array.h
> @@ -0,0 +1,325 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef _LINUX_FLEX_ARRAY_H_
> +#define _LINUX_FLEX_ARRAY_H_

You missed at least overflow.h and errno.h here.

> +#include <linux/string.h>

And this...

> +/* Make sure we compose correctly with KASAN. */
> +#ifndef __underlying_memcpy
> +#define __underlying_memcpy     __builtin_memcpy
> +#endif
> +#ifndef __underlying_memset
> +#define __underlying_memset	__builtin_memset
> +#endif
> +
> +/*
> + * A "flexible array structure" (FAS) is a structure which ends with a
> + * flexible array member (FAM) _and_ contains another member that represents
> + * how many array elements are present in the struct instance's flexible
> + * array member:
> + *
> + * struct flex_array_struct_example {
> + *	...		// arbitrary members
> + *	u16 part_count;	// count of elements stored in "parts" below.
> + *	...		// arbitrary members
> + *	u32 parts[];	// flexible array member containing u32 elements.
> + * };
> + *
> + * Without the "count of elements" member, a structure ending with a
> + * flexible array has no way to check its own size, and should be
> + * considered just a blob of memory that is length-checked through some
> + * other means. Kernel structures with flexible arrays should strive to
> + * always be true flexible array structures so that they can be operated
> + * on with the flex*()-family of helpers defined below.
> + *
> + * To use the normal flex*() helpers, prepare for future C syntax that
> + * would identify a flex array's count member directly, and keep kernel
> + * declarations minimized, a common declaration, bounded_flex_array(), is
> + * provided:
> + *
> + * struct flex_array_struct_example {
> + *	...			 // arbitrary members
> + *	bounded_flex_array(
> + *		u16, part_count, // count of elements stored in "parts" below.
> + *		u32, parts	 // flexible array with elements of type u32.
> + *	);
> + * );
> + *
> + * To handle the less common case when the count member cannot be made a
> + * neighbor of the flex array, either the extended flex*() helpers can be
> + * used, or the members can also be declared separately:
> + *
> + * struct flex_array_struct_example {
> + *	...		// position-sensitive members
> + *	// count of elements stored in "parts" below.
> + *	DECLARE_FAS_COUNT(u16, part_count);
> + *	..		// position-sensitive members
> + *	// flexible array with elements of type u32.
> + *	DECLARE_FAS_ARRAY(u32, parts);
> + * };
> + *
> + * The above two declaration styles will create an alias for part_count as
> + * __flex_array_elements_count and for parts as __flex_array_elements,
> + * which are used internally to avoid needing to repeat the member names
> + * as arguments to the normal flex*() helpers.
> + *
> + * The extended flex*() helpers are designed to be used in the less common
> + * situations when the member aliases are not available, especially in two
> + * flexible array struct layout situations: "encapsulated" and "split".
> + *
> + * An "encapsulated flexible array structure" is a structure that contains
> + * a full "flexible array structure" as its final struct member. These are
> + * used frequently when needing to pass around a copy of a flexible array
> + * structure, and track other things about the data outside of the scope of
> + * the flexible array structure itself:
> + *
> + * struct encapsulated_example {
> + *	...		// arbitrary members
> + *	struct flex_array_struct_example fas;
> + * };
> + *
> + * A "split flexible array structure" is like an encapsulated flexible
> + * array struct, but the element count member is at a different level,
> + * separate from the struct that contains the flexible array:
> + *
> + * struct blob_example {
> + *	...		// arbitrary members
> + *	u32 parts[];	// flexible array with elements of type u32.
> + * };
> + *
> + * struct split_example {
> + *	...		// arbitrary members
> + *	u16 part_count;	// count of elements stored in "parts" below.
> + *	...		// arbitrary members
> + *	struct blob_example blob;
> + * };
> + *
> + * In the case where the element count member is not stored in native
> + * CPU format, the extended helpers can be used to specify the to/from
> + * cpu helper needed to do the conversions.
> + *
> + * Examples of using flex_cpy():
> + *
> + *	struct simple {
> + *		u32 flags;
> + *		bounded_flex_array(
> + *			u32,	count,
> + *			u8,	data
> + *		);
> + *	};
> + *	struct hardware_defined {
> + *		DECLARE_FAS_COUNT(u32, count);
> + *		u32 state;
> + *		u32 flags;
> + *		DECLARE_FAS_ARRAY(u8, data);
> + *	};
> + *
> + *	int do_simple(struct simple *src) {
> + *		struct simple *ptr_simple = NULL;
> + *		struct hardware_defined *ptr_hw = NULL;
> + *
> + *		...allocation of ptr_simple happens...
> + *		ptr_simple->count = src->count;
> + *		rc = flex_cpy(ptr_simple, src);
> + *		...
> + *		...allocation of ptr_hw happens...
> + *		ptr_hw->count = src->count;
> + *		rc = flex_cpy(ptr_hw, src);
> + *		...
> + *	}
> + *
> + *	struct blob {
> + *		u32 flags;
> + *		u8 data[];
> + *	};
> + *	struct split {
> + *		be32 count;
> + *		struct blob blob;
> + *	};
> + *	struct split *ptr_split = NULL;
> + *
> + *	int do_split(struct split *src) {
> + *		struct split *ptr = NULL;
> + *
> + *		...allocation of ptr happens...
> + *		ptr->count = src->count;
> + *		rc = __flex_cpy(ptr, src, blob.data, count, be32_to_cpu);
> + *		...
> + *	}
> + *
> + */
> +
> +/* Wrappers around the UAPI macros. */
> +#define bounded_flex_array(COUNT_TYPE, COUNT_NAME, ARRAY_TYPE, ARRAY_NAME) \
> +	__DECLARE_FAS_COUNT(COUNT_TYPE, COUNT_NAME);	\
> +	__DECLARE_FAS_ARRAY(ARRAY_TYPE, ARRAY_NAME)
> +
> +#define DECLARE_FAS_COUNT(TYPE, NAME)			\
> +	__DECLARE_FAS_COUNT(TYPE, NAME)
> +
> +#define DECLARE_FAS_ARRAY(TYPE, NAME)			\
> +	__DECLARE_FAS_ARRAY(TYPE, NAME)
> +
> +/* All the helpers return negative on failure, and must be checked. */
> +static inline int __must_check __must_check_errno(int err)
> +{
> +	return err;
> +}
> +
> +#define __passthru(x)	(x)
> +
> +/**
> + * __fas_elements_bytes - Calculate potential size of the flexible
> + *			  array elements of a given flexible array
> + *			  structure
> + *
> + * @p: Pointer to flexible array structure.
> + * @flex_member: Member name of the flexible array elements.
> + * @count_member: Member name of the flexible array elements count.
> + * @elements_count: Count of proposed number of @p->__flex_array_elements
> + * @bytes: Pointer to variable to write calculation of total size in bytes.
> + *
> + * Returns: 0 on successful calculation, -ve on error.
> + *
> + * This performs the same calculation as flex_array_size(), except
> + * that the result is bounds checked and written to @bytes instead
> + * of being returned.
> + */
> +#define __fas_elements_bytes(p, flex_member, count_member,		\
> +			     elements_count, bytes)			\
> +__must_check_errno(({							\
> +	int __feb_err = -EINVAL;					\
> +	size_t __feb_elements_count = (elements_count);			\
> +	size_t __feb_elements_max =					\
> +		type_max(typeof((p)->count_member));			\
> +	if (__feb_elements_count > __feb_elements_max ||		\
> +	    check_mul_overflow(sizeof(*(p)->flex_member),		\
> +			       __feb_elements_count, bytes)) {		\
> +		*(bytes) = 0;						\
> +		__feb_err = -E2BIG;					\
> +	} else {							\
> +		__feb_err = 0;						\
> +	}								\
> +	__feb_err;							\
> +}))
> +
> +/**
> + * fas_elements_bytes - Calculate current size of the flexible array
> + *			elements of a given flexible array structure
> + *
> + * @p: Pointer to flexible array structure.
> + * @bytes: Pointer to variable to write calculation of total size in bytes.
> + *
> + * Returns: 0 on successful calculation, -ve on error.
> + *
> + * This performs the same calculation as flex_array_size(), except
> + * that the result is bounds checked and written to @bytes instead
> + * of being returned.
> + */
> +#define fas_elements_bytes(p, bytes)					\
> +	__fas_elements_bytes(p, __flex_array_elements,			\
> +			     __flex_array_elements_count,		\
> +			     (p)->__flex_array_elements_count, bytes)
> +
> +/**
> + * __fas_bytes - Calculate potential size of flexible array structure
> + *
> + * @p: Pointer to flexible array structure.
> + * @flex_member: Member name of the flexible array elements.
> + * @count_member: Member name of the flexible array elements count.
> + * @elements_count: Count of proposed number of @p->__flex_array_elements
> + * @bytes: Pointer to variable to write calculation of total size in bytes.
> + *
> + * Returns: 0 on successful calculation, -ve on error.
> + *
> + * This performs the same calculation as struct_size(), except
> + * that the result is bounds checked and written to @bytes instead
> + * of being returned.
> + */
> +#define __fas_bytes(p, flex_member, count_member, elements_count, bytes)\
> +__must_check_errno(({							\
> +	int __fasb_err;							\
> +	typeof(*bytes) __fasb_bytes;					\
> +									\
> +	if (__fas_elements_bytes(p, flex_member, count_member,		\
> +				 elements_count, &__fasb_bytes) ||	\
> +	    check_add_overflow(sizeof(*(p)), __fasb_bytes, bytes)) {	\
> +		*(bytes) = 0;						\
> +		__fasb_err = -E2BIG;					\
> +	} else {							\
> +		__fasb_err = 0;						\
> +	}								\
> +	__fasb_err;							\
> +}))
> +
> +/**
> + * fas_bytes - Calculate current size of flexible array structure
> + *
> + * @p: Pointer to flexible array structure.
> + * @bytes: Pointer to variable to write calculation of total size in bytes.
> + *
> + * This performs the same calculation as struct_size(), except
> + * that the result is bounds checked and written to @bytes instead
> + * of being returned, using the current size of the flexible array
> + * structure (via @p->__flexible_array_elements_count).
> + *
> + * Returns: 0 on successful calculation, -ve on error.
> + */
> +#define fas_bytes(p, bytes)						\
> +	__fas_bytes(p, __flex_array_elements,				\
> +		    __flex_array_elements_count,			\
> +		    (p)->__flex_array_elements_count, bytes)
> +
> +/**
> + * flex_cpy - Copy from one flexible array struct into another
> + *
> + * @dst: Destination pointer
> + * @src: Source pointer
> + *
> + * The full structure of @src will be copied to @dst, including all trailing
> + * flexible array elements. @dst->__flex_array_elements_count must be large
> + * enough to hold @src->__flex_array_elements_count. Any elements left over
> + * in @dst will be zero-wiped.
> + *
> + * Returns: 0 on successful calculation, -ve on error.
> + */
> +#define __flex_cpy(dst, src, elements_member, count_member, to_cpu)	\
> +__must_check_errno(({							\
> +	int __fc_err = -EINVAL;						\
> +	typeof(*(dst)) *__fc_dst = (dst);				\
> +	typeof(*(src)) *__fc_src = (src);				\
> +	size_t __fc_dst_bytes, __fc_src_bytes;				\
> +									\
> +	BUILD_BUG_ON(!__same_type(*(__fc_dst), *(__fc_src)));		\
> +									\
> +	do {								\
> +		if (__fas_bytes(__fc_dst,				\
> +				elements_member,			\
> +				count_member,				\
> +				to_cpu(__fc_dst->count_member),		\
> +				&__fc_dst_bytes) ||			\
> +		    __fas_bytes(__fc_src,				\
> +				elements_member,			\
> +				count_member,				\
> +				to_cpu(__fc_src->count_member),		\
> +				&__fc_src_bytes) ||			\
> +		    __fc_dst_bytes < __fc_src_bytes) {			\
> +			/* do we need to wipe dst here? */		\
> +			__fc_err = -E2BIG;				\
> +			break;						\
> +		}							\
> +		__underlying_memcpy(__fc_dst, __fc_src, __fc_src_bytes);\
> +		/* __flex_array_elements_count is included in memcpy */	\
> +		/* Wipe any now-unused trailing elements in @dst: */	\
> +		__underlying_memset((u8 *)__fc_dst + __fc_src_bytes, 0,	\
> +				 __fc_dst_bytes - __fc_src_bytes);	\
> +		__fc_err = 0;						\
> +	} while (0);							\
> +	__fc_err;							\
> +}))
> +
> +#define flex_cpy(dst, src)						\
> +	__flex_cpy(dst, src, __flex_array_elements,			\
> +		   __flex_array_elements_count, __passthru)
> +
> +#endif /* _LINUX_FLEX_ARRAY_H_ */
> diff --git a/include/linux/string.h b/include/linux/string.h
> index cf7607b32102..9277f9e2a432 100644
> --- a/include/linux/string.h
> +++ b/include/linux/string.h
> @@ -256,6 +256,7 @@ static inline const char *kbasename(const char *path)
>  #define unsafe_memcpy(dst, src, bytes, justification)		\
>  	memcpy(dst, src, bytes)
>  #endif
> +#include <linux/flex_array.h>

...seems to be a hidden mine for the future. Can we avoid dependency hell, please?

>  void memcpy_and_pad(void *dest, size_t dest_len, const void *src, size_t count,
>  		    int pad);
> diff --git a/include/uapi/linux/stddef.h b/include/uapi/linux/stddef.h
> index 7837ba4fe728..e16afe1951d8 100644
> --- a/include/uapi/linux/stddef.h
> +++ b/include/uapi/linux/stddef.h
> @@ -44,4 +44,18 @@
>  		struct { } __empty_ ## NAME; \
>  		TYPE NAME[]; \
>  	}
> +
> +/* For use with flexible array structure helpers, in <linux/flex_array.h> */
> +#define __DECLARE_FAS_COUNT(TYPE, NAME)					\
> +	union {								\
> +		TYPE __flex_array_elements_count;			\
> +		TYPE NAME;						\
> +	}
> +
> +#define __DECLARE_FAS_ARRAY(TYPE, NAME)					\
> +	union {								\
> +		__DECLARE_FLEX_ARRAY(TYPE, __flex_array_elements);	\
> +		__DECLARE_FLEX_ARRAY(TYPE, NAME);			\
> +	}
> +
>  #endif
> -- 
> 2.34.1
> 

-- 
With Best Regards,
Andy Shevchenko


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ