From: "Kiryl Shutsemau (Meta)" <[email protected]>
vma_flags_t is one unsigned long on 32-bit -- NUM_VMA_FLAG_BITS ==
BITS_PER_LONG by design, so VM_xxx-declared bits sit in the first
word and hit the single-long fast path. But the bit enum declares
some bits unconditionally above BITS_PER_LONG (VMA_UFFD_MINOR_BIT
== 41 today, with VM_UFFD_MINOR == VM_NONE on 32-bit so no VMA
actually carries the bit).
Passing such a bit to mk_vma_flags() goes through __set_bit(41,
&one_long) and writes one word past the end. The compiler folds
the OOB store with wraparound (1UL << (41 % 32) == bit 9) into
the first word. Bit 9 is already in __VMA_UFFD_FLAGS so the mask
happens to come out right today, but any high-numbered bit whose
mod-BITS_PER_LONG position is otherwise unused would silently OR
an extra bit into the mask.
Add VMA_NO_BIT and have DECLARE_VMA_BIT() resolve any bitnum out
of range to it. vma_flags_set_flag() drops negative bit values.
The ternary collapses at compile time, the runtime check folds
away when the bit is in range, and the common path is unchanged.
Bits declared in the enum are now safe to pass to mk_vma_flags()
regardless of arch.
Fixes: 9ea35a25d51b ("mm: introduce VMA flags bitmap type")
Cc: [email protected]
Signed-off-by: Kiryl Shutsemau <[email protected]>
---
include/linux/mm.h | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 0f2612a70fb1..71b11945e4fc 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -286,8 +286,17 @@ extern unsigned int kobjsize(const void *objp);
*/
typedef int __bitwise vma_flag_t;
-#define DECLARE_VMA_BIT(name, bitnum) \
- VMA_ ## name ## _BIT = ((__force vma_flag_t)bitnum)
+/*
+ * VMA_NO_BIT means "no bit"; mk_vma_flags() skips it. DECLARE_VMA_BIT()
+ * below uses it for any bit number that doesn't fit in the bitmap, so
+ * callers don't need to track which bits are valid on the current build.
+ */
+#define VMA_NO_BIT ((__force vma_flag_t)-1)
+
+#define DECLARE_VMA_BIT(name, bitnum) \
+ VMA_ ## name ## _BIT = (((bitnum) < NUM_VMA_FLAG_BITS) ? \
+ ((__force vma_flag_t)(bitnum)) : \
+ VMA_NO_BIT)
#define DECLARE_VMA_BIT_ALIAS(name, aliased) \
VMA_ ## name ## _BIT = (VMA_ ## aliased ## _BIT)
enum {
@@ -1081,6 +1090,8 @@ static __always_inline void
vma_flags_set_flag(vma_flags_t *flags,
{
unsigned long *bitmap = flags->__vma_flags;
+ if ((__force int)bit < 0)
+ return;
__set_bit((__force int)bit, bitmap);
}
--
2.54.0