Re: [PATCH v9 1/8] overflow: Move and add few utility macros into overflow

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 





On 8/26/22 1:47 AM, Kees Cook wrote:
On Wed, Aug 24, 2022 at 05:45:07PM +0900, Gwan-gyeong Mun wrote:
It moves overflows_type utility macro into overflow header from i915_utils
header. The overflows_type can be used to catch the truncaion (overflow)
between different data types. And it adds check_assign() macro which
performs an assigning source value into destination ptr along with an
overflow check. overflow_type macro has been improved to handle the signbit
by gcc's built-in overflow check function. And it adds overflows_ptr()
helper macro for checking the overflows between a value and a pointer
type/value.

v3: Add is_type_unsigned() macro (Mauro)
     Modify overflows_type() macro to consider signed data types (Mauro)
     Fix the problem that safe_conversion() macro always returns true
v4: Fix kernel-doc markups
v6: Move macro addition location so that it can be used by other than drm
     subsystem (Jani, Mauro, Andi)
     Change is_type_unsigned to is_unsigned_type to have the same name form
     as is_signed_type macro
v8: Add check_assign() and remove safe_conversion() (Kees)
     Fix overflows_type() to use gcc's built-in overflow function (Andrzej)
     Add overflows_ptr() to allow overflow checking when assigning a value
     into a pointer variable (G.G.)
v9: Fix overflows_type() to use __builtin_add_overflow() instead of
     __builtin_add_overflow_p() (Andrzej)
     Fix overflows_ptr() to use overflows_type() with the unsigned long type
     (Andrzej)

Signed-off-by: Gwan-gyeong Mun <gwan-gyeong.mun@xxxxxxxxx>
Cc: Thomas Hellström <thomas.hellstrom@xxxxxxxxxxxxxxx>
Cc: Matthew Auld <matthew.auld@xxxxxxxxx>
Cc: Nirmoy Das <nirmoy.das@xxxxxxxxx>
Cc: Jani Nikula <jani.nikula@xxxxxxxxx>
Cc: Andi Shyti <andi.shyti@xxxxxxxxxxxxxxx>
Cc: Andrzej Hajda <andrzej.hajda@xxxxxxxxx>
Cc: Mauro Carvalho Chehab <mauro.chehab@xxxxxxxxxxxxxxx>
Cc: Kees Cook <keescook@xxxxxxxxxxxx>
Reviewed-by: Mauro Carvalho Chehab <mchehab@xxxxxxxxxx> (v5)
---
  drivers/gpu/drm/i915/i915_user_extensions.c |  3 +-
  drivers/gpu/drm/i915/i915_utils.h           |  5 +-
  include/linux/overflow.h                    | 62 +++++++++++++++++++++
  3 files changed, 64 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/i915/i915_user_extensions.c b/drivers/gpu/drm/i915/i915_user_extensions.c
index c822d0aafd2d..6f6b5b910968 100644
--- a/drivers/gpu/drm/i915/i915_user_extensions.c
+++ b/drivers/gpu/drm/i915/i915_user_extensions.c
@@ -50,8 +50,7 @@ int i915_user_extensions(struct i915_user_extension __user *ext,
  		if (err)
  			return err;
- if (get_user(next, &ext->next_extension) ||
-		    overflows_type(next, ext))
+		if (get_user(next, &ext->next_extension) || overflows_ptr(next))
  			return -EFAULT;
ext = u64_to_user_ptr(next);

I continue to dislike the layers of macros and specialization here.
This is just a fancy version of check_assign():

	if (get_user(next, &ext->next_extension) || check_assign(next, &ext))
		return -EFAULT;

However, the __builtin_*_overflow() family only wants to work on
integral types, so this needs to be slightly expanded:

	uintptr_t kptr;
	...
	if (get_user(next, &ext->next_extension) || check_assign(next, &kptr))
		return -EFAULT;

	ext = (void * __user)kptr;

But, it does seem like the actual problem here is that u64_to_user_ptr()
is not performing the checking? It's used heavily in the drm code.

Is a check_assign_user_ptr() needed?
Hi Kees,
Yes, the reason that an additional overflow check is performed when assigning a pointer type is that no overflow check is provided when using u64_to_user_ptr()) I also agree with the explicit overflow check when assigning to pointer type variables.
I'll add check_assign_user_ptr() to the new version of the patch and use it.

diff --git a/drivers/gpu/drm/i915/i915_utils.h b/drivers/gpu/drm/i915/i915_utils.h
index c10d68cdc3ca..eb0ded23fa9c 100644
--- a/drivers/gpu/drm/i915/i915_utils.h
+++ b/drivers/gpu/drm/i915/i915_utils.h
@@ -32,6 +32,7 @@
  #include <linux/types.h>
  #include <linux/workqueue.h>
  #include <linux/sched/clock.h>
+#include <linux/overflow.h>
#ifdef CONFIG_X86
  #include <asm/hypervisor.h>
@@ -111,10 +112,6 @@ bool i915_error_injected(void);
  #define range_overflows_end_t(type, start, size, max) \
  	range_overflows_end((type)(start), (type)(size), (type)(max))
-/* Note we don't consider signbits :| */
-#define overflows_type(x, T) \
-	(sizeof(x) > sizeof(T) && (x) >> BITS_PER_TYPE(T))
-
  #define ptr_mask_bits(ptr, n) ({					\
  	unsigned long __v = (unsigned long)(ptr);			\
  	(typeof(ptr))(__v & -BIT(n));					\
diff --git a/include/linux/overflow.h b/include/linux/overflow.h
index f1221d11f8e5..6af9df1d67a1 100644
--- a/include/linux/overflow.h
+++ b/include/linux/overflow.h
@@ -52,6 +52,68 @@ static inline bool __must_check __must_check_overflow(bool overflow)
  	return unlikely(overflow);
  }
+/**
+ * overflows_type - helper for checking the overflows between data types or
+ *                  values
+ *
+ * @x: Source value or data type for overflow check
+ * @T: Destination value or data type for overflow check
+ *
+ * It compares the values or data type between the first and second argument to
+ * check whether overflow can occur when assigning the first argument to the
+ * variable of the second argument. Source and Destination can be singned or
+ * unsigned data types. Source and Destination can be different data types.
+ *
+ * Returns:
+ * True if overflow can occur, false otherwise.
+ */
+#define overflows_type(x, T) __must_check_overflow(({	\
+	typeof(T) v = 0;				\
+	__builtin_add_overflow((x), v, &v);		\
+}))

I'd like to avoid "externalizing" these kinds of checks when the better
path is to catch the issue at operation type (add, sub, mul, assign).
Looking at existing users, I see stuff like:

I'll update overflows_type() with updated  check_add_overflow()
( https://lore.kernel.org/all/202208311040.C6CA8253@keescook )
                 if (overflows_type(item.query_id - 1, unsigned long))
                         return -EINVAL;

                 func_idx = item.query_id - 1;

This requires too much open-coded checking, IMO. It's better as:

		if (check_assign(item.query_id - 1, &func_idx))
			return -EINVAL;

or other similar:

         if (overflows_type(user->slice_mask, context->slice_mask) ||
	...
         context->slice_mask = user->slice_mask;

and some that don't make sense to me. Why check argument types? Is this
trying to avoid implicit type conversions?

So, if it's _really_ needed, I can live with adding overflows_type().

thanks for checking in detail.
Yes, it is thought that the above-mentioned codes can be modified using check_assign(). And it seems that it can be applied as an additional patch after check_assign() is applied.

And the code in the form below

if (overflows_type(size, obj->base.size))
		return ERR_PTR(-E2BIG);

It seems that it can be fixed by updating type_max/type_min/__type_half_max.

#define __type_half_max(x) (((typeof(x))1) << (BITS_PER_TYPE(x) - 1 - is_signed_type(x))) #define type_max(x) ((typeof(x))((__type_half_max(x) - 1) + __type_half_max(x)))
#define type_min(x) ((typeof(x))((typeof(x))-type_max(x)-(typeof(x))1))

  if (size > type_max(obj->base.size))
	return ERR_PTR(-E2BIG);

And when a type is used as an argument, it is used for comparison with the size used internally (not directly exposed outside the data structure or accessible through pointer referencing in multiple steps). It seems that max_type can be used in these cases as well, but when it is used to check before calling a function that actually allocates memory using overflows_type(), it seems that readability( or intuitively understood)can be good.

And in the alloc_table(), the function calls "GEM_BUG_ON(overflows_type(count * PAGE_SIZE, sg->length));", it is a case of checking and creating while looping. In this case, overflows_type() seems convenient.

In all the confirmed cases by me, it is possible to avoid using overflows_type() by changing or modifying the code in a different form.

In all the confirmed cases by me, it is possible to avoid using overflows_type() by changing or modifying the code in a different form. but there are also codes that can be easily or intuitively understood when added, so in my opinion, it would be good to add overflows_type() macro.
+
+/**
+ * overflows_ptr - helper for checking the occurrence of overflows when a value
+ *                 assigns to  the pointer data type
+ *
+ * @x: Source value for overflow check
+ *
+ * gcc's built-in overflow check functions don't support checking between the
+ * pointer type and non-pointer type. And ILP32 and LP64 have the same bit size
+ * between long and pointer. Therefore it internally compares the source value
+ * and unsigned long data type for checking overflow.
+ *
+ * Returns:
+ * True if overflow can occur, false otherwise.
+ */
+#define overflows_ptr(x) __must_check_overflow(overflows_type(x, unsigned long))

I'd rather not have this -- it's just a specialized use of
overflows_type(), and only used in 1 place.

okay I'll drop it.
+
+/**
+ * check_assign - perform an assigning source value into destination ptr along
+ *                with an overflow check.
+ *
+ * @value: Source value
+ * @ptr: Destination pointer address, If the pointer type is not used,
+ *       a warning message is output during build.
+ *
+ * It checks internally the ptr is a pointer type. And it uses gcc's built-in
+ * overflow check function.
+ * It does not use the check_*() wrapper functions, but directly uses gcc's
+ * built-in overflow check function so that it can be used even when
+ * the type of value and the type pointed to by ptr are different without build
+ * warning messages.

This is a good point: the check_{add,sub,mul}_overflow() helpers
currently require all the params be the same type, which rather limits
their usage. Perhaps this can be weakened now that we're not using[1]
the fallback logic any more? (Separate patch.)

+ *
+ * Returns:
+ * If the value would overflow the destination, it returns true. If not return
+ * false. When overflow does not occur, the assigning into ptr from value
+ * succeeds. It follows the return policy as other check_*_overflow() functions
+ * return non-zero as a failure.
+ */
+#define check_assign(value, ptr) __must_check_overflow(({	\
+	typecheck_pointer(ptr); 		\
+	__builtin_add_overflow(0, value, ptr);	\
+}))

But yes, this looks correct. I really like it. :)

+
  /*
   * For simplicity and code hygiene, the fallback code below insists on
   * a, b and *d having the same type (similar to the min() and max()
--
2.37.1


-Kees

[1] 4eb6bd55cfb2 ("compiler.h: drop fallback overflow checkers")




[Index of Archives]     [Linux DRI Users]     [Linux Intel Graphics]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux