kcsan -Wmaybe-uninitialized warning in ntfs3

Arnd Bergmann
2026-04-21T07:54:29Z

ntfs3 started enabling -Wmaybe-uninitialized, a warning that is generally
useful but is normally disabled in the kernel because of too many
false positives when used with sanitizers and some other gcc options.

I ran into one such warning in today's linux-next:

CC fs/ntfs3/file.o
In file included from /home/arnd/arm-soc/include/asm-generic/rwonce.h:27,
from /home/arnd/arm-soc/arch/arm64/include/asm/rwonce.h:81,
from /home/arnd/arm-soc/include/linux/compiler.h:369,
from /home/arnd/arm-soc/include/linux/array_size.h:5,
from /home/arnd/arm-soc/include/linux/kernel.h:16,
from /home/arnd/arm-soc/include/linux/backing-dev.h:12,
from /home/arnd/arm-soc/fs/ntfs3/file.c:10:
In function 'instrument_copy_from_user_before',
inlined from '_inline_copy_from_user' at /home/arnd/arm-soc/include/linux/uaccess.h:184:2,
inlined from 'copy_from_user' at /home/arnd/arm-soc/include/linux/uaccess.h:221:9,
inlined from 'ntfs_ioctl_fitrim' at /home/arnd/arm-soc/fs/ntfs3/file.c:77:6,
inlined from 'ntfs_ioctl' at /home/arnd/arm-soc/fs/ntfs3/file.c:164:10:
/home/arnd/arm-soc/include/linux/kcsan-checks.h:220:28: error: 'range' may be used uninitialized [-Werror=maybe-uninitialized]
220 | #define kcsan_check_access __kcsan_check_access
| ^
/home/arnd/arm-soc/include/linux/kcsan-checks.h:311:9: note: in expansion of macro 'kcsan_check_access'
311 | kcsan_check_access(ptr, size, KCSAN_ACCESS_WRITE)
| ^~~~~~~~~~~~~~~~~~
/home/arnd/arm-soc/include/linux/instrumented.h:147:9: note: in expansion of macro 'kcsan_check_write'
147 | kcsan_check_write(to, n);
| ^~~~~~~~~~~~~~~~~
/home/arnd/arm-soc/include/linux/kcsan-checks.h: In function 'ntfs_ioctl':
/home/arnd/arm-soc/include/linux/kcsan-checks.h:37:6: note: by argument 1 of type 'const volatile void *' to '__kcsan_check_access' declared here
37 | void __kcsan_check_access(const volatile void *ptr, size_t size, int type);
| ^~~~~~~~~~~~~~~~~~~~
/home/arnd/arm-soc/fs/ntfs3/file.c:65:29: note: 'range' declared here
65 | struct fstrim_range range;
| ^~~~~

What happens here is that copy_from_user() is used in a very
normal way to copy a user space structure into a previously
uninitialized on-stack buffer. With KCSAN enabled, this passes
the pointer to that buffer into __kcsan_check_access(), which
takes a 'const volatile void *' pointer. Gcc correctly notices
that there is no way to access the data behind this pointer
when it hasn't been initialized yet, as both read and write
would cause undefined behavior.

I'm not sure what a good solution would be to avoid this,
but I assume this should be fixed in the kcsan instrumentation.
I tried changing that code to pass non-const pointers for
any instrument_write variant, which does avoid the warning,
but also adds a bit of complexity, see below.

Any other ideas?

Arnd

diff --git a/include/linux/instrumented.h b/include/linux/instrumented.h
index a1b4cf81adc2..d28aa6b0aeb8 100644
--- a/include/linux/instrumented.h
+++ b/include/linux/instrumented.h
@@ -36,7 +36,7 @@ static __always_inline void instrument_read(const volatile void *v, size_t size)
* Instrument a regular write access. The instrumentation should be inserted
* before the actual write happens.
*/
-static __always_inline void instrument_write(const volatile void *v, size_t size)
+static __always_inline void instrument_write(volatile void *v, size_t size)
{
kasan_check_write(v, size);
kcsan_check_write(v, size);
@@ -50,7 +50,7 @@ static __always_inline void instrument_write(const volatile void *v, size_t size
* Instrument a regular write access. The instrumentation should be inserted
* before the actual write happens.
*/
-static __always_inline void instrument_read_write(const volatile void *v, size_t size)
+static __always_inline void instrument_read_write(volatile void *v, size_t size)
{
kasan_check_write(v, size);
kcsan_check_read_write(v, size);
@@ -92,7 +92,7 @@ static __always_inline void instrument_atomic_read(const volatile void *v, size_
* Instrument an atomic write access. The instrumentation should be inserted
* before the actual write happens.
*/
-static __always_inline void instrument_atomic_write(const volatile void *v, size_t size)
+static __always_inline void instrument_atomic_write(volatile void *v, size_t size)
{
kasan_check_write(v, size);
kcsan_check_atomic_write(v, size);
@@ -107,7 +107,7 @@ static __always_inline void instrument_atomic_write(const volatile void *v, size
* Instrument an atomic read-write access. The instrumentation should be
* inserted before the actual write happens.
*/
-static __always_inline void instrument_atomic_read_write(const volatile void *v, size_t size)
+static __always_inline void instrument_atomic_read_write(volatile void *v, size_t size)
{
kasan_check_write(v, size);
kcsan_check_atomic_read_write(v, size);
@@ -141,7 +141,7 @@ instrument_copy_to_user(void __user *to, const void *from, unsigned long n)
* variants). The instrumentation should be inserted before the accesses.
*/
static __always_inline void
-instrument_copy_from_user_before(const void *to, const void __user *from, unsigned long n)
+instrument_copy_from_user_before(void *to, const void __user *from, unsigned long n)
{
kasan_check_write(to, n);
kcsan_check_write(to, n);
diff --git a/include/linux/kcsan-checks.h b/include/linux/kcsan-checks.h
index 92f3843d9ebb..e598fd9679a9 100644
--- a/include/linux/kcsan-checks.h
+++ b/include/linux/kcsan-checks.h
@@ -34,7 +34,8 @@
* @size: size of access
* @type: access type modifier
*/
-void __kcsan_check_access(const volatile void *ptr, size_t size, int type);
+void __kcsan_check_read_access(const volatile void *ptr, size_t size, int type);
+void __kcsan_check_write_access(volatile void *ptr, size_t size, int type);

/*
* See definition of __tsan_atomic_signal_fence() in kernel/kcsan/core.c.
@@ -186,8 +187,8 @@ void kcsan_end_scoped_access(struct kcsan_scoped_access *sa);

#else /* CONFIG_KCSAN */

-static inline void __kcsan_check_access(const volatile void *ptr, size_t size,
- int type) { }
+static inline void __kcsan_check_read_access(const volatile void *ptr, size_t size, int type) { }
+static inline void __kcsan_check_write_access(volatile void *ptr, size_t size, int type) { }

static inline void __kcsan_mb(void) { }
static inline void __kcsan_wmb(void) { }
@@ -217,7 +218,8 @@ static inline void kcsan_end_scoped_access(struct kcsan_scoped_access *sa) { }
* Only calls into the runtime when the particular compilation unit has KCSAN
* instrumentation enabled. May be used in header files.
*/
-#define kcsan_check_access __kcsan_check_access
+#define kcsan_check_write_access __kcsan_check_write_access
+#define kcsan_check_read_access __kcsan_check_read_access

/*
* Only use these to disable KCSAN for accesses in the current compilation unit;
@@ -226,8 +228,8 @@ static inline void kcsan_end_scoped_access(struct kcsan_scoped_access *sa) { }
#define __kcsan_disable_current kcsan_disable_current
#define __kcsan_enable_current kcsan_enable_current_nowarn
#else /* __SANITIZE_THREAD__ */
-static inline void kcsan_check_access(const volatile void *ptr, size_t size,
- int type) { }
+static inline void kcsan_check_read_access(const volatile void *ptr, size_t size, int type) { }
+static inline void kcsan_check_write_access(volatile void *ptr, size_t size, int type) { }
static inline void __kcsan_enable_current(void) { }
static inline void __kcsan_disable_current(void) { }
#endif /* __SANITIZE_THREAD__ */
@@ -273,7 +275,7 @@ static inline void __kcsan_disable_current(void) { }
* @ptr: address of access
* @size: size of access
*/
-#define __kcsan_check_read(ptr, size) __kcsan_check_access(ptr, size, 0)
+#define __kcsan_check_read(ptr, size) __kcsan_check_read_access(ptr, size, 0)

/**
* __kcsan_check_write - check regular write access for races
@@ -282,7 +284,7 @@ static inline void __kcsan_disable_current(void) { }
* @size: size of access
*/
#define __kcsan_check_write(ptr, size) \
- __kcsan_check_access(ptr, size, KCSAN_ACCESS_WRITE)
+ __kcsan_check_write_access(ptr, size, KCSAN_ACCESS_WRITE)

/**
* __kcsan_check_read_write - check regular read-write access for races
@@ -291,7 +293,7 @@ static inline void __kcsan_disable_current(void) { }
* @size: size of access
*/
#define __kcsan_check_read_write(ptr, size) \
- __kcsan_check_access(ptr, size, KCSAN_ACCESS_COMPOUND | KCSAN_ACCESS_WRITE)
+ __kcsan_check_read_access(ptr, size, KCSAN_ACCESS_COMPOUND | KCSAN_ACCESS_WRITE)

/**
* kcsan_check_read - check regular read access for races
@@ -299,7 +301,7 @@ static inline void __kcsan_disable_current(void) { }
* @ptr: address of access
* @size: size of access
*/
-#define kcsan_check_read(ptr, size) kcsan_check_access(ptr, size, 0)
+#define kcsan_check_read(ptr, size) kcsan_check_read_access(ptr, size, 0)

/**
* kcsan_check_write - check regular write access for races
@@ -308,7 +310,7 @@ static inline void __kcsan_disable_current(void) { }
* @size: size of access
*/
#define kcsan_check_write(ptr, size) \
- kcsan_check_access(ptr, size, KCSAN_ACCESS_WRITE)
+ kcsan_check_write_access(ptr, size, KCSAN_ACCESS_WRITE)

/**
* kcsan_check_read_write - check regular read-write access for races
@@ -317,7 +319,7 @@ static inline void __kcsan_disable_current(void) { }
* @size: size of access
*/
#define kcsan_check_read_write(ptr, size) \
- kcsan_check_access(ptr, size, KCSAN_ACCESS_COMPOUND | KCSAN_ACCESS_WRITE)
+ kcsan_check_write_access(ptr, size, KCSAN_ACCESS_COMPOUND | KCSAN_ACCESS_WRITE)

/*
* Check for atomic accesses: if atomic accesses are not ignored, this simply
@@ -329,11 +331,11 @@ static inline void __kcsan_disable_current(void) { }
#define kcsan_check_atomic_read_write(...) do { } while (0)
#else
#define kcsan_check_atomic_read(ptr, size) \
- kcsan_check_access(ptr, size, KCSAN_ACCESS_ATOMIC)
+ kcsan_check_read_access(ptr, size, KCSAN_ACCESS_ATOMIC)
#define kcsan_check_atomic_write(ptr, size) \
- kcsan_check_access(ptr, size, KCSAN_ACCESS_ATOMIC | KCSAN_ACCESS_WRITE)
+ kcsan_check_write_access(ptr, size, KCSAN_ACCESS_ATOMIC | KCSAN_ACCESS_WRITE)
#define kcsan_check_atomic_read_write(ptr, size) \
- kcsan_check_access(ptr, size, KCSAN_ACCESS_ATOMIC | KCSAN_ACCESS_WRITE | KCSAN_ACCESS_COMPOUND)
+ kcsan_check_write_access(ptr, size, KCSAN_ACCESS_ATOMIC | KCSAN_ACCESS_WRITE | KCSAN_ACCESS_COMPOUND)
#endif

/**
@@ -368,7 +370,7 @@ static inline void __kcsan_disable_current(void) { }
* @var: variable to assert on
*/
#define ASSERT_EXCLUSIVE_WRITER(var) \
- __kcsan_check_access(&(var), sizeof(var), KCSAN_ACCESS_ASSERT)
+ __kcsan_check_write_access(&(var), sizeof(var), KCSAN_ACCESS_ASSERT)

/*
* Helper macros for implementation of for ASSERT_EXCLUSIVE_*_SCOPED(). @id is
@@ -449,7 +451,7 @@ static inline void __kcsan_disable_current(void) { }
* @var: variable to assert on
*/
#define ASSERT_EXCLUSIVE_ACCESS(var) \
- __kcsan_check_access(&(var), sizeof(var), KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT)
+ __kcsan_check_read_access(&(var), sizeof(var), KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT)

/**
* ASSERT_EXCLUSIVE_ACCESS_SCOPED - assert no concurrent accesses to @var in scope
@@ -525,7 +527,7 @@ static inline void __kcsan_disable_current(void) { }
#define ASSERT_EXCLUSIVE_BITS(var, mask) \
do { \
kcsan_set_access_mask(mask); \
- __kcsan_check_access(&(var), sizeof(var), KCSAN_ACCESS_ASSERT);\
+ __kcsan_check_read_access(&(var), sizeof(var), KCSAN_ACCESS_ASSERT);\
kcsan_set_access_mask(0); \
kcsan_atomic_next(1); \
} while (0)
diff --git a/kernel/kcsan/core.c b/kernel/kcsan/core.c
index 8a7baf4e332e..289634c3de5c 100644
--- a/kernel/kcsan/core.c
+++ b/kernel/kcsan/core.c
@@ -953,11 +953,17 @@ void kcsan_end_scoped_access(struct kcsan_scoped_access *sa)
}
EXPORT_SYMBOL(kcsan_end_scoped_access);

-void __kcsan_check_access(const volatile void *ptr, size_t size, int type)
+void __kcsan_check_read_access(const volatile void *ptr, size_t size, int type)
{
check_access(ptr, size, type, _RET_IP_);
}
-EXPORT_SYMBOL(__kcsan_check_access);
+EXPORT_SYMBOL(__kcsan_check_read_access);
+
+void __kcsan_check_write_access(volatile void *ptr, size_t size, int type)
+{
+ check_access(ptr, size, type, _RET_IP_);
+}
+EXPORT_SYMBOL(__kcsan_check_write_access);

#define DEFINE_MEMORY_BARRIER(name, order_before_cond) \
void __kcsan_##name(void) \
diff --git a/kernel/kcsan/debugfs.c b/kernel/kcsan/debugfs.c
index 2af39ba5b70b..665a06bc37a7 100644
--- a/kernel/kcsan/debugfs.c
+++ b/kernel/kcsan/debugfs.c
@@ -75,7 +75,7 @@ static noinline void microbenchmark(unsigned long iters)
unsigned long addr = iters & ((PAGE_SIZE << 8) - 1);
int type = !(iters & 0x7f) ? KCSAN_ACCESS_ATOMIC :
(!(iters & 0xf) ? KCSAN_ACCESS_WRITE : 0);
- __kcsan_check_access((void *)addr, sizeof(long), type);
+ __kcsan_check_write_access((void *)addr, sizeof(long), type);
}
cycles = get_cycles() - cycles;

diff --git a/mm/slub.c b/mm/slub.c
index 161079ac5ba1..b1dcfc575407 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -2621,8 +2621,8 @@ bool slab_free_hook(struct kmem_cache *s, void *x, bool init,

/* Use KCSAN to help debug racy use-after-free. */
if (!still_accessible)
- __kcsan_check_access(x, s->object_size,
- KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT);
+ __kcsan_check_write_access(x, s->object_size,
+ KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT);

if (kfence_free(x))
return false;
diff --git a/mm/vma_init.c b/mm/vma_init.c
index 3c0b65950510..248e9042a6af 100644
--- a/mm/vma_init.c
+++ b/mm/vma_init.c
@@ -125,7 +125,7 @@ struct vm_area_struct *vm_area_dup(struct vm_area_struct *orig)
if (!new)
return NULL;

- ASSERT_EXCLUSIVE_WRITER(orig->vm_flags);
+ ASSERT_EXCLUSIVE_WRITER(orig->flags);
ASSERT_EXCLUSIVE_WRITER(orig->vm_file);
vm_area_init_from(orig, new);

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 9b11cf3193b9..7a3650fb10db 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -1155,7 +1155,8 @@ static const char *uaccess_safe_builtin[] = {
"__asan_report_store8_noabort",
"__asan_report_store16_noabort",
/* KCSAN */
- "__kcsan_check_access",
+ "__kcsan_check_read_access",
+ "__kcsan_check_write_access",
"__kcsan_mb",
"__kcsan_wmb",
"__kcsan_rmb",