Compare commits

...

10 Commits

Author SHA1 Message Date
Simon Glass
5a52202acc ext4: Check for overflow when allocating tables
An overflow could cause too little memory to be allocated, thus
potentially causing undefined behaviour. Add a check for overflow.

Since blk_bmaps and inode_bmaps use the same size, share the variable.

Series-to: u-boot
Series-cc: heinrich
Series-version: 3
Series-changes: 2
- Use Linux macros instead of gcc built-ins

Cover-changes: 3
- Split out into separate patches for easier review

Cover-letter:
ext4: Overflow fixes
This series fixes some potential overflows in the ext4 code with invalid
or malicious filesystems.

To assist with this and future work, it brings in overflow.h and part of
limits.h from Linux.
END

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-10 13:10:58 +02:00
Simon Glass
7fc9be7f50 ext4: Make sure there is at least one sector per block
The implementation assumes that a sector can fit in a block, so add a
check for this.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-10 12:57:19 +02:00
Simon Glass
2fbe4c6659 ext4: Set a max limit on block-size in ext4fs_init()
The block size is typically around 4K. Complain if it is much larger,
since this can cause problems with 32-bit arithmetic.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-10 12:57:19 +02:00
Simon Glass
921cd46f37 ext4: Replace multiplication with an existing variable
Since gdsize_total is known to be a multiple of blksz, there is no need
to recompute the value later in the function. Just use the variable, to
avoid what might look like an overflow.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-10 12:57:16 +02:00
Simon Glass
513a4da96a ext4: Bail out of no_blk_pergdt() if there are no blocks
If the size is too small, return an error, since no_blk_pergdt is
assumed to be non-zero in the following code.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-10 12:57:14 +02:00
Simon Glass
6e65ef788c ext4: Check for overflow of block count
An overflow could cause too little memory to be allocated, thus
potentially causing undefined behaviour. Add a check for overflow.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-10 12:57:11 +02:00
Simon Glass
c34680883a ext4: Create a var for the number of blocks to allocate
Since this expression might overflow, put it in a variable.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-10 12:57:08 +02:00
Simon Glass
ddfb42e69b ext4: Avoid expr in declaration in ext4fs_get_bgdtable()
Move the expression into the body of the function so the ordering can
be adjusted.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-10 12:57:04 +02:00
Simon Glass
6d87fe0cda Bring in linux/overflow.h
Add this file from Linux v6.14 so we can use it to add overflow checks,
rather than using compiler built-ins directly.

Series-changes: 2
- Add new patch to bring in linux/overflow.h

Signed-off-by: Simon Glass <simon.glass@canonical.com>
Suggested-by: Tom Rini <trini@konsulko.com>
Reviewed-by: Tom Rini <trini@konsulko.com>
2025-05-10 12:22:03 +02:00
Simon Glass
f045526e82 Bring in linux/limits.h
Add part of this file from Linux v6.14 so we can include it from the
linux/overflow.h file. Drop the relevant section from kernel.h

The top of the file is omitted since it defines things used in mbedos
and we get an error on SIZE_MAX.

Commit-notes:
Error is:

  In file included from include/limits.h:6,
                 from lib/mbedtls/external/mbedtls/library/constant_time.c:14:
  include/linux/limits.h:7:35: error: missing binary operator before token "0"
    7 | #define SIZE_MAX        (~(size_t)0)
      |                                   ^
  lib/mbedtls/external/mbedtls/library/constant_time_internal.h:65:6: note: in expansion of macro ‘SIZE_MAX’
   65 | #if (SIZE_MAX > 0xffffffffffffffffULL)
      |      ^~~~~~~~
  include/linux/limits.h:7:35: error: missing binary operator before token "0"
    7 | #define SIZE_MAX        (~(size_t)0)
      |                                   ^
  lib/mbedtls/external/mbedtls/library/constant_time_internal.h:71:8: note: in expansion of macro ‘SIZE_MAX’
   71 | #elif (SIZE_MAX > 0xffffffff) || defined(MBEDTLS_HAVE_INT64)
      |        ^~~~~~~~
  make[3]: *** [scripts/Makefile.build:256: lib/mbedtls/external/mbedtls/library/constant_time.o] Error 1
  make[2]: *** [scripts/Makefile.build:398: lib/mbedtls] Error 2
END

Series-changes: 2
- Add new patch to bring in linux/limits.h

Signed-off-by: Simon Glass <simon.glass@canonical.com>
2025-05-10 12:13:20 +02:00
5 changed files with 487 additions and 16 deletions

View File

@@ -25,6 +25,7 @@
#include <malloc.h>
#include <memalign.h>
#include <part.h>
#include <linux/overflow.h>
#include <linux/stat.h>
#include <div64.h>
#include "ext4_common.h"
@@ -108,8 +109,15 @@ int ext4fs_get_bgdtable(void)
{
int status;
struct ext_filesystem *fs = get_fs();
int gdsize_total = ROUND(fs->no_blkgrp * fs->gdsize, fs->blksz);
size_t alloc_size;
int gdsize_total;
if (check_mul_overflow(fs->no_blkgrp, fs->gdsize, &alloc_size))
return -1;
gdsize_total = ROUND(alloc_size, fs->blksz);
fs->no_blk_pergdt = gdsize_total / fs->blksz;
if (!fs->no_blk_pergdt)
return -1;
/* allocate memory for gdtable */
fs->gdtable = zalloc(gdsize_total);
@@ -117,7 +125,7 @@ int ext4fs_get_bgdtable(void)
return -ENOMEM;
/* read the group descriptor table */
status = ext4fs_devread((lbaint_t)fs->gdtable_blkno * fs->sect_perblk,
0, fs->blksz * fs->no_blk_pergdt, fs->gdtable);
0, gdsize_total, fs->gdtable);
if (status == 0)
goto fail;
@@ -599,10 +607,17 @@ int ext4fs_init(void)
int i;
uint32_t real_free_blocks = 0;
struct ext_filesystem *fs = get_fs();
size_t alloc_size;
/* check for a reasonable block size, no more than 64K */
if (LOG2_BLOCK_SIZE(ext4fs_root) > 16)
goto fail;
/* populate fs */
fs->blksz = EXT2_BLOCK_SIZE(ext4fs_root);
fs->sect_perblk = fs->blksz >> fs->dev_desc->log2blksz;
if (!fs->sect_perblk)
goto fail;
/* get the superblock */
fs->sb = zalloc(SUPERBLOCK_SIZE);
@@ -629,7 +644,9 @@ int ext4fs_init(void)
}
/* load all the available bitmap block of the partition */
fs->blk_bmaps = zalloc(fs->no_blkgrp * sizeof(char *));
if (check_mul_overflow(fs->no_blkgrp, sizeof(char *), &alloc_size))
goto fail;
fs->blk_bmaps = zalloc(alloc_size);
if (!fs->blk_bmaps)
goto fail;
for (i = 0; i < fs->no_blkgrp; i++) {
@@ -649,7 +666,7 @@ int ext4fs_init(void)
}
/* load all the available inode bitmap of the partition */
fs->inode_bmaps = zalloc(fs->no_blkgrp * sizeof(unsigned char *));
fs->inode_bmaps = zalloc(alloc_size);
if (!fs->inode_bmaps)
goto fail;
for (i = 0; i < fs->no_blkgrp; i++) {

View File

@@ -3,6 +3,8 @@
#ifndef _LIMITS_H
#define _LIMITS_H
#include <linux/limits.h>
#define INT_MAX 0x7fffffff
#define UINT_MAX 0xffffffffU
#define CHAR_BIT 8

View File

@@ -16,18 +16,6 @@
#define LLONG_MIN (-LLONG_MAX - 1)
#define ULLONG_MAX (~0ULL)
#define U8_MAX ((u8)~0U)
#define S8_MAX ((s8)(U8_MAX>>1))
#define S8_MIN ((s8)(-S8_MAX - 1))
#define U16_MAX ((u16)~0U)
#define S16_MAX ((s16)(U16_MAX>>1))
#define S16_MIN ((s16)(-S16_MAX - 1))
#define U32_MAX ((u32)~0U)
#define S32_MAX ((s32)(U32_MAX>>1))
#define S32_MIN ((s32)(-S32_MAX - 1))
#define U64_MAX ((u64)~0ULL)
#define S64_MAX ((s64)(U64_MAX>>1))
#define S64_MIN ((s64)(-S64_MAX - 1))
#define INT32_MAX S32_MAX

21
include/linux/limits.h Normal file
View File

@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_LIMITS_H
#define _LINUX_LIMITS_H
#include <linux/types.h>
#define U8_MAX ((u8)~0U)
#define S8_MAX ((s8)(U8_MAX >> 1))
#define S8_MIN ((s8)(-S8_MAX - 1))
#define U16_MAX ((u16)~0U)
#define S16_MAX ((s16)(U16_MAX >> 1))
#define S16_MIN ((s16)(-S16_MAX - 1))
#define U32_MAX ((u32)~0U)
#define U32_MIN ((u32)0)
#define S32_MAX ((s32)(U32_MAX >> 1))
#define S32_MIN ((s32)(-S32_MAX - 1))
#define U64_MAX ((u64)~0ULL)
#define S64_MAX ((s64)(U64_MAX >> 1))
#define S64_MIN ((s64)(-S64_MAX - 1))
#endif /* _LINUX_LIMITS_H */

443
include/linux/overflow.h Normal file
View File

@@ -0,0 +1,443 @@
/* SPDX-License-Identifier: GPL-2.0 OR MIT */
#ifndef __LINUX_OVERFLOW_H
#define __LINUX_OVERFLOW_H
#include <linux/compiler.h>
#include <linux/limits.h>
#include <linux/const.h>
/*
* We need to compute the minimum and maximum values representable in a given
* type. These macros may also be useful elsewhere. It would seem more obvious
* to do something like:
*
* #define type_min(T) (T)(is_signed_type(T) ? (T)1 << (8*sizeof(T)-1) : 0)
* #define type_max(T) (T)(is_signed_type(T) ? ((T)1 << (8*sizeof(T)-1)) - 1 : ~(T)0)
*
* Unfortunately, the middle expressions, strictly speaking, have
* undefined behaviour, and at least some versions of gcc warn about
* the type_max expression (but not if -fsanitize=undefined is in
* effect; in that case, the warning is deferred to runtime...).
*
* The slightly excessive casting in type_min is to make sure the
* macros also produce sensible values for the exotic type _Bool. [The
* overflow checkers only almost work for _Bool, but that's
* a-feature-not-a-bug, since people shouldn't be doing arithmetic on
* _Bools. Besides, the gcc builtins don't allow _Bool* as third
* argument.]
*
* Idea stolen from
* https://mail-index.netbsd.org/tech-misc/2007/02/05/0000.html -
* credit to Christian Biere.
*/
#define __type_half_max(type) ((type)1 << (8*sizeof(type) - 1 - is_signed_type(type)))
#define __type_max(T) ((T)((__type_half_max(T) - 1) + __type_half_max(T)))
#define type_max(t) __type_max(typeof(t))
#define __type_min(T) ((T)((T)-type_max(T)-(T)1))
#define type_min(t) __type_min(typeof(t))
/*
* Avoids triggering -Wtype-limits compilation warning,
* while using unsigned data types to check a < 0.
*/
#define is_non_negative(a) ((a) > 0 || (a) == 0)
#define is_negative(a) (!(is_non_negative(a)))
/*
* Allows for effectively applying __must_check to a macro so we can have
* both the type-agnostic benefits of the macros while also being able to
* enforce that the return value is, in fact, checked.
*/
static inline bool __must_check __must_check_overflow(bool overflow)
{
return unlikely(overflow);
}
/**
* check_add_overflow() - Calculate addition with overflow checking
* @a: first addend
* @b: second addend
* @d: pointer to store sum
*
* Returns true on wrap-around, false otherwise.
*
* *@d holds the results of the attempted addition, regardless of whether
* wrap-around occurred.
*/
#define check_add_overflow(a, b, d) \
__must_check_overflow(__builtin_add_overflow(a, b, d))
/**
* wrapping_add() - Intentionally perform a wrapping addition
* @type: type for result of calculation
* @a: first addend
* @b: second addend
*
* Return the potentially wrapped-around addition without
* tripping any wrap-around sanitizers that may be enabled.
*/
#define wrapping_add(type, a, b) \
({ \
type __val; \
__builtin_add_overflow(a, b, &__val); \
__val; \
})
/**
* wrapping_assign_add() - Intentionally perform a wrapping increment assignment
* @var: variable to be incremented
* @offset: amount to add
*
* Increments @var by @offset with wrap-around. Returns the resulting
* value of @var. Will not trip any wrap-around sanitizers.
*
* Returns the new value of @var.
*/
#define wrapping_assign_add(var, offset) \
({ \
typeof(var) *__ptr = &(var); \
*__ptr = wrapping_add(typeof(var), *__ptr, offset); \
})
/**
* check_sub_overflow() - Calculate subtraction with overflow checking
* @a: minuend; value to subtract from
* @b: subtrahend; value to subtract from @a
* @d: pointer to store difference
*
* Returns true on wrap-around, false otherwise.
*
* *@d holds the results of the attempted subtraction, regardless of whether
* wrap-around occurred.
*/
#define check_sub_overflow(a, b, d) \
__must_check_overflow(__builtin_sub_overflow(a, b, d))
/**
* wrapping_sub() - Intentionally perform a wrapping subtraction
* @type: type for result of calculation
* @a: minuend; value to subtract from
* @b: subtrahend; value to subtract from @a
*
* Return the potentially wrapped-around subtraction without
* tripping any wrap-around sanitizers that may be enabled.
*/
#define wrapping_sub(type, a, b) \
({ \
type __val; \
__builtin_sub_overflow(a, b, &__val); \
__val; \
})
/**
* wrapping_assign_sub() - Intentionally perform a wrapping decrement assign
* @var: variable to be decremented
* @offset: amount to subtract
*
* Decrements @var by @offset with wrap-around. Returns the resulting
* value of @var. Will not trip any wrap-around sanitizers.
*
* Returns the new value of @var.
*/
#define wrapping_assign_sub(var, offset) \
({ \
typeof(var) *__ptr = &(var); \
*__ptr = wrapping_sub(typeof(var), *__ptr, offset); \
})
/**
* check_mul_overflow() - Calculate multiplication with overflow checking
* @a: first factor
* @b: second factor
* @d: pointer to store product
*
* Returns true on wrap-around, false otherwise.
*
* *@d holds the results of the attempted multiplication, regardless of whether
* wrap-around occurred.
*/
#define check_mul_overflow(a, b, d) \
__must_check_overflow(__builtin_mul_overflow(a, b, d))
/**
* wrapping_mul() - Intentionally perform a wrapping multiplication
* @type: type for result of calculation
* @a: first factor
* @b: second factor
*
* Return the potentially wrapped-around multiplication without
* tripping any wrap-around sanitizers that may be enabled.
*/
#define wrapping_mul(type, a, b) \
({ \
type __val; \
__builtin_mul_overflow(a, b, &__val); \
__val; \
})
/**
* check_shl_overflow() - Calculate a left-shifted value and check overflow
* @a: Value to be shifted
* @s: How many bits left to shift
* @d: Pointer to where to store the result
*
* Computes *@d = (@a << @s)
*
* Returns true if '*@d' cannot hold the result or when '@a << @s' doesn't
* make sense. Example conditions:
*
* - '@a << @s' causes bits to be lost when stored in *@d.
* - '@s' is garbage (e.g. negative) or so large that the result of
* '@a << @s' is guaranteed to be 0.
* - '@a' is negative.
* - '@a << @s' sets the sign bit, if any, in '*@d'.
*
* '*@d' will hold the results of the attempted shift, but is not
* considered "safe for use" if true is returned.
*/
#define check_shl_overflow(a, s, d) __must_check_overflow(({ \
typeof(a) _a = a; \
typeof(s) _s = s; \
typeof(d) _d = d; \
unsigned long long _a_full = _a; \
unsigned int _to_shift = \
is_non_negative(_s) && _s < 8 * sizeof(*d) ? _s : 0; \
*_d = (_a_full << _to_shift); \
(_to_shift != _s || is_negative(*_d) || is_negative(_a) || \
(*_d >> _to_shift) != _a); \
}))
#define __overflows_type_constexpr(x, T) ( \
is_unsigned_type(typeof(x)) ? \
(x) > type_max(T) : \
is_unsigned_type(typeof(T)) ? \
(x) < 0 || (x) > type_max(T) : \
(x) < type_min(T) || (x) > type_max(T))
#define __overflows_type(x, T) ({ \
typeof(T) v = 0; \
check_add_overflow((x), v, &v); \
})
/**
* overflows_type - helper for checking the overflows between value, variables,
* or data type
*
* @n: source constant value or variable to be checked
* @T: destination variable or data type proposed to store @x
*
* Compares the @x expression for whether or not it can safely fit in
* the storage of the type in @T. @x and @T can have different types.
* If @x is a constant expression, this will also resolve to a constant
* expression.
*
* Returns: true if overflow can occur, false otherwise.
*/
#define overflows_type(n, T) \
__builtin_choose_expr(__is_constexpr(n), \
__overflows_type_constexpr(n, T), \
__overflows_type(n, T))
/**
* castable_to_type - like __same_type(), but also allows for casted literals
*
* @n: variable or constant value
* @T: variable or data type
*
* Unlike the __same_type() macro, this allows a constant value as the
* first argument. If this value would not overflow into an assignment
* of the second argument's type, it returns true. Otherwise, this falls
* back to __same_type().
*/
#define castable_to_type(n, T) \
__builtin_choose_expr(__is_constexpr(n), \
!__overflows_type_constexpr(n, T), \
__same_type(n, T))
/**
* size_mul() - Calculate size_t multiplication with saturation at SIZE_MAX
* @factor1: first factor
* @factor2: second factor
*
* Returns: calculate @factor1 * @factor2, both promoted to size_t,
* with any overflow causing the return value to be SIZE_MAX. The
* lvalue must be size_t to avoid implicit type conversion.
*/
static inline size_t __must_check size_mul(size_t factor1, size_t factor2)
{
size_t bytes;
if (check_mul_overflow(factor1, factor2, &bytes))
return SIZE_MAX;
return bytes;
}
/**
* size_add() - Calculate size_t addition with saturation at SIZE_MAX
* @addend1: first addend
* @addend2: second addend
*
* Returns: calculate @addend1 + @addend2, both promoted to size_t,
* with any overflow causing the return value to be SIZE_MAX. The
* lvalue must be size_t to avoid implicit type conversion.
*/
static inline size_t __must_check size_add(size_t addend1, size_t addend2)
{
size_t bytes;
if (check_add_overflow(addend1, addend2, &bytes))
return SIZE_MAX;
return bytes;
}
/**
* size_sub() - Calculate size_t subtraction with saturation at SIZE_MAX
* @minuend: value to subtract from
* @subtrahend: value to subtract from @minuend
*
* Returns: calculate @minuend - @subtrahend, both promoted to size_t,
* with any overflow causing the return value to be SIZE_MAX. For
* composition with the size_add() and size_mul() helpers, neither
* argument may be SIZE_MAX (or the result with be forced to SIZE_MAX).
* The lvalue must be size_t to avoid implicit type conversion.
*/
static inline size_t __must_check size_sub(size_t minuend, size_t subtrahend)
{
size_t bytes;
if (minuend == SIZE_MAX || subtrahend == SIZE_MAX ||
check_sub_overflow(minuend, subtrahend, &bytes))
return SIZE_MAX;
return bytes;
}
/**
* array_size() - Calculate size of 2-dimensional array.
* @a: dimension one
* @b: dimension two
*
* Calculates size of 2-dimensional array: @a * @b.
*
* Returns: number of bytes needed to represent the array or SIZE_MAX on
* overflow.
*/
#define array_size(a, b) size_mul(a, b)
/**
* array3_size() - Calculate size of 3-dimensional array.
* @a: dimension one
* @b: dimension two
* @c: dimension three
*
* Calculates size of 3-dimensional array: @a * @b * @c.
*
* Returns: number of bytes needed to represent the array or SIZE_MAX on
* overflow.
*/
#define array3_size(a, b, c) size_mul(size_mul(a, b), c)
/**
* flex_array_size() - Calculate size of a flexible array member
* within an enclosing structure.
* @p: Pointer to the structure.
* @member: Name of the flexible array member.
* @count: Number of elements in the array.
*
* Calculates size of a flexible array of @count number of @member
* elements, at the end of structure @p.
*
* Return: number of bytes needed or SIZE_MAX on overflow.
*/
#define flex_array_size(p, member, count) \
__builtin_choose_expr(__is_constexpr(count), \
(count) * sizeof(*(p)->member) + __must_be_array((p)->member), \
size_mul(count, sizeof(*(p)->member) + __must_be_array((p)->member)))
/**
* struct_size() - Calculate size of structure with trailing flexible array.
* @p: Pointer to the structure.
* @member: Name of the array member.
* @count: Number of elements in the array.
*
* Calculates size of memory needed for structure of @p followed by an
* array of @count number of @member elements.
*
* Return: number of bytes needed or SIZE_MAX on overflow.
*/
#define struct_size(p, member, count) \
__builtin_choose_expr(__is_constexpr(count), \
sizeof(*(p)) + flex_array_size(p, member, count), \
size_add(sizeof(*(p)), flex_array_size(p, member, count)))
/**
* struct_size_t() - Calculate size of structure with trailing flexible array
* @type: structure type name.
* @member: Name of the array member.
* @count: Number of elements in the array.
*
* Calculates size of memory needed for structure @type followed by an
* array of @count number of @member elements. Prefer using struct_size()
* when possible instead, to keep calculations associated with a specific
* instance variable of type @type.
*
* Return: number of bytes needed or SIZE_MAX on overflow.
*/
#define struct_size_t(type, member, count) \
struct_size((type *)NULL, member, count)
/**
* _DEFINE_FLEX() - helper macro for DEFINE_FLEX() family.
* Enables caller macro to pass (different) initializer.
*
* @type: structure type name, including "struct" keyword.
* @name: Name for a variable to define.
* @member: Name of the array member.
* @count: Number of elements in the array; must be compile-time const.
* @initializer: initializer expression (could be empty for no init).
*/
#define _DEFINE_FLEX(type, name, member, count, initializer...) \
_Static_assert(__builtin_constant_p(count), \
"onstack flex array members require compile-time const count"); \
union { \
u8 bytes[struct_size_t(type, member, count)]; \
type obj; \
} name##_u initializer; \
type *name = (type *)&name##_u
/**
* DEFINE_RAW_FLEX() - Define an on-stack instance of structure with a trailing
* flexible array member, when it does not have a __counted_by annotation.
*
* @type: structure type name, including "struct" keyword.
* @name: Name for a variable to define.
* @member: Name of the array member.
* @count: Number of elements in the array; must be compile-time const.
*
* Define a zeroed, on-stack, instance of @type structure with a trailing
* flexible array member.
* Use __struct_size(@name) to get compile-time size of it afterwards.
*/
#define DEFINE_RAW_FLEX(type, name, member, count) \
_DEFINE_FLEX(type, name, member, count, = {})
/**
* DEFINE_FLEX() - Define an on-stack instance of structure with a trailing
* flexible array member.
*
* @TYPE: structure type name, including "struct" keyword.
* @NAME: Name for a variable to define.
* @MEMBER: Name of the array member.
* @COUNTER: Name of the __counted_by member.
* @COUNT: Number of elements in the array; must be compile-time const.
*
* Define a zeroed, on-stack, instance of @TYPE structure with a trailing
* flexible array member.
* Use __struct_size(@NAME) to get compile-time size of it afterwards.
*/
#define DEFINE_FLEX(TYPE, NAME, MEMBER, COUNTER, COUNT) \
_DEFINE_FLEX(TYPE, NAME, MEMBER, COUNT, = { .obj.COUNTER = COUNT, })
#endif /* __LINUX_OVERFLOW_H */