From: quanyeyang syzbot reported KCSAN write-write races when two tasks concurrently update the same map value. Both accesses reach the ordinary memcpy() paths in bpf_obj_memcpy() through copy_map_value(). Unlocked map value updates are intentionally not serialized. Therefore, a concurrent update may result in a torn value. Callers requiring consistency must provide external synchronization or use BPF_F_LOCK where supported. bpf_long_memcpy() already documents and annotates the same behavior for long-aligned copies. Annotate the ordinary memcpy() operations in bpf_obj_memcpy() with data_race() as well. This documents the existing concurrency semantics and prevents KCSAN from reporting these intentional races. It does not add synchronization or change map update behavior. Place the annotations at the memcpy() sites in the common helper, matching bpf_long_memcpy(), without changing the existing copy helper interfaces. Reported-by: syzbot+44044637ef892e79ca2b@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=44044637ef892e79ca2b Signed-off-by: quanyeyang --- For this V1, the annotations are placed in the common bpf_obj_memcpy() helper. This also covers callers that may already provide synchronization. Restricting the annotations to known unlocked callers would require propagating the concurrency context through the copy_map_value() call chain or introducing separate copy helpers. I kept the existing interfaces unchanged for V1 and would appreciate feedback on whether a narrower annotation is preferred. There are two classes of expected checkpatch warnings: - DATA_RACE is reported for the three annotations because checkpatch only recognizes an immediately adjacent comment. Their common rationale is documented once above bpf_obj_memcpy(). - MISSING_FIXES_TAG is reported because the commit references syzkaller. No Fixes tag is included because this documents long-standing intentional lockless semantics rather than a regression introduced by a particular commit. --- include/linux/bpf.h | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 7719f6528445..b37573b9d01d 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -560,7 +560,16 @@ static inline void bpf_long_memcpy(void *dst, const void *src, u32 size) data_race(*ldst++ = *lsrc++); } -/* copy everything but bpf_spin_lock, bpf_timer, and kptrs. There could be one of each. */ +/* + * Copy everything but bpf_spin_lock, bpf_timer, and kptrs. There could + * be one of each. + * + * Map value copies can race with unlocked updates from BPF programs or + * syscalls. Such copies are best effort and may be torn, so annotate the + * ordinary byte copies as intentional data races. Callers that require a + * consistent value must provide synchronization or use BPF_F_LOCK where + * supported. + */ static inline void bpf_obj_memcpy(struct btf_record *rec, void *dst, void *src, u32 size, bool long_memcpy) @@ -572,7 +581,7 @@ static inline void bpf_obj_memcpy(struct btf_record *rec, if (long_memcpy) bpf_long_memcpy(dst, src, round_up(size, 8)); else - memcpy(dst, src, size); + data_race(memcpy(dst, src, size)); return; } @@ -580,10 +589,10 @@ static inline void bpf_obj_memcpy(struct btf_record *rec, u32 next_off = rec->fields[i].offset; u32 sz = next_off - curr_off; - memcpy(dst + curr_off, src + curr_off, sz); + data_race(memcpy(dst + curr_off, src + curr_off, sz)); curr_off += rec->fields[i].size + sz; } - memcpy(dst + curr_off, src + curr_off, size - curr_off); + data_race(memcpy(dst + curr_off, src + curr_off, size - curr_off)); } static inline void copy_map_value(struct bpf_map *map, void *dst, void *src) --- base-commit: bd5f485f3f026225b86573e559af0b7254ef4184 change-id: 20260819-bpf-kcsan-obj-memcpy-67042b1fce7d Best regards, -- quanyeyang