Root Cause Analysis
1. Racing Object and Field
The reported data race occurs on struct file::f_pos (loff_t, byte offset 0x68 / 104 bytes in include/linux/fs.h:1280) on an open file description (struct file) shared between multiple threads.
The two racing execution paths are:
2. Mechanism & Invariant Violation
For regular files (FMODE_ATOMIC_POS), POSIX and Linux VFS semantics require serialized updates to file->f_pos when the struct file is shared across threads:
ksys_write() uses CLASS(fd_pos, f)(fd) (fs/read_write.c:710), which invokes fdget_pos() (fs/file.c:1406). Since the file is shared (__file_ref_read_raw(&file->f_ref) != FILE_REF_ONEREF), file_needs_f_pos_lock(file) returns true, and mutex_lock(&file->f_pos_lock) is acquired for the duration of the write. At line 721, fd_file(f)->f_pos is written while holding f_pos_lock.
do_sendfile() (fs/read_write.c:1283-1377) resolves its file descriptors using CLASS(fd, in)(in_fd) and CLASS(fd, out)(out_fd) (include/linux/file.h:84), which resolve to standard fdget() calls without acquiring file->f_pos_lock on either the input or output file.
- When
sendfile64(out_fd, in_fd, offset, count) is called with offset == NULL (fs/read_write.c:1412), do_sendfile() is passed ppos = NULL. In this mode, do_sendfile() reads fd_file(in)->f_pos at line 1302 and writes the updated position back to fd_file(in)->f_pos at line 1369.
Because do_sendfile() does not acquire f_pos_lock (or use atomic accessors), its store to fd_file(in)->f_pos directly races with the store in ksys_write() whenever in_fd in sendfile64() refers to the same underlying struct file being concurrently written by ksys_write().