fs: add real_fs to track task's actual fs_struct

Add a real_fs field to task_struct that always mirrors the fs field.
This lays the groundwork for distinguishing between a task's permanent
fs_struct and one that is temporarily overridden via scoped_with_init_fs().

When a kthread temporarily overrides current->fs for path lookup, we
need to know the original fs_struct for operations like exit_fs() and
unshare_fs_struct() that must operate on the real, permanent fs.

For now real_fs is always equal to fs. It is maintained alongside fs in
all the relevant paths: exit_fs(), unshare_fs_struct(),
switch_fs_struct(), and copy_fs().

Link: https://patch.msgid.link/20260601-work-kthread-nullfs-v4-4-77ee053060e0@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2026-06-01 15:56:37 +02:00
parent 67c54d1d73
commit 1d4ee94a51
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
8 changed files with 26 additions and 13 deletions

View File

@ -61,7 +61,7 @@ void chroot_fs_refs(const struct path *old_root, const struct path *new_root)
read_lock(&tasklist_lock);
for_each_process_thread(g, p) {
task_lock(p);
fs = p->fs;
fs = p->real_fs;
if (fs) {
int hits = 0;
write_seqlock(&fs->seq);
@ -89,12 +89,13 @@ void free_fs_struct(struct fs_struct *fs)
void exit_fs(struct task_struct *tsk)
{
struct fs_struct *fs = tsk->fs;
struct fs_struct *fs = tsk->real_fs;
if (fs) {
int kill;
task_lock(tsk);
read_seqlock_excl(&fs->seq);
tsk->real_fs = NULL;
tsk->fs = NULL;
kill = !--fs->users;
read_sequnlock_excl(&fs->seq);
@ -126,7 +127,7 @@ struct fs_struct *copy_fs_struct(struct fs_struct *old)
int unshare_fs_struct(void)
{
struct fs_struct *fs = current->fs;
struct fs_struct *fs = current->real_fs;
struct fs_struct *new_fs = copy_fs_struct(fs);
int kill;
@ -135,8 +136,10 @@ int unshare_fs_struct(void)
task_lock(current);
read_seqlock_excl(&fs->seq);
VFS_WARN_ON_ONCE(fs != current->fs);
kill = !--fs->users;
current->fs = new_fs;
current->real_fs = new_fs;
read_sequnlock_excl(&fs->seq);
task_unlock(current);
@ -177,8 +180,10 @@ struct fs_struct *switch_fs_struct(struct fs_struct *new_fs)
scoped_guard(task_lock, current) {
fs = current->fs;
VFS_WARN_ON_ONCE(fs != current->real_fs);
read_seqlock_excl(&fs->seq);
current->fs = new_fs;
current->real_fs = new_fs;
if (--fs->users)
new_fs = NULL;
else

View File

@ -168,8 +168,8 @@ static inline void task_state(struct seq_file *m, struct pid_namespace *ns,
cred = get_task_cred(p);
task_lock(p);
if (p->fs)
umask = p->fs->umask;
if (p->real_fs)
umask = p->real_fs->umask;
if (p->files)
max_fds = files_fdtable(p->files)->max_fds;
task_unlock(p);

View File

@ -211,8 +211,8 @@ static int get_task_root(struct task_struct *task, struct path *root)
int result = -ENOENT;
task_lock(task);
if (task->fs) {
get_fs_root(task->fs, root);
if (task->real_fs) {
get_fs_root(task->real_fs, root);
result = 0;
}
task_unlock(task);
@ -225,8 +225,8 @@ static int proc_cwd_link(struct dentry *dentry, struct path *path,
int result = -ENOENT;
task_lock(task);
if (task->fs) {
get_fs_pwd(task->fs, path);
if (task->real_fs) {
get_fs_pwd(task->real_fs, path);
result = 0;
}
task_unlock(task);

View File

@ -254,13 +254,13 @@ static int mounts_open_common(struct inode *inode, struct file *file,
}
ns = nsp->mnt_ns;
get_mnt_ns(ns);
if (!task->fs) {
if (!task->real_fs) {
task_unlock(task);
put_task_struct(task);
ret = -ENOENT;
goto err_put_ns;
}
get_fs_root(task->fs, &root);
get_fs_root(task->real_fs, &root);
task_unlock(task);
put_task_struct(task);

View File

@ -1191,6 +1191,7 @@ struct task_struct {
unsigned long last_switch_time;
#endif
/* Filesystem information: */
struct fs_struct *real_fs;
struct fs_struct *fs;
/* Open file information: */

View File

@ -162,6 +162,7 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = {
RCU_POINTER_INITIALIZER(cred, &init_cred),
.comm = INIT_TASK_COMM,
.thread = INIT_THREAD,
.real_fs = &init_fs,
.fs = &init_fs,
.files = &init_files,
#ifdef CONFIG_IO_URING

View File

@ -1616,6 +1616,8 @@ static int copy_exec_state(u64 clone_flags, struct task_struct *tsk)
static int copy_fs(u64 clone_flags, struct task_struct *tsk)
{
struct fs_struct *fs = current->fs;
VFS_WARN_ON_ONCE(current->fs != current->real_fs);
if (clone_flags & CLONE_FS) {
/* tsk->fs is already what we want */
read_seqlock_excl(&fs->seq);
@ -1628,7 +1630,7 @@ static int copy_fs(u64 clone_flags, struct task_struct *tsk)
read_sequnlock_excl(&fs->seq);
return 0;
}
tsk->fs = copy_fs_struct(fs);
tsk->real_fs = tsk->fs = copy_fs_struct(fs);
if (!tsk->fs)
return -ENOMEM;
return 0;
@ -3246,6 +3248,10 @@ int ksys_unshare(unsigned long unshare_flags)
if (unshare_flags & CLONE_NEWNS)
unshare_flags |= CLONE_FS;
/* No unsharing with overriden fs state */
VFS_WARN_ON_ONCE(unshare_flags & (CLONE_NEWNS | CLONE_FS) &&
current->fs != current->real_fs);
err = check_unshare_flags(unshare_flags);
if (err)
goto bad_unshare_out;

View File

@ -186,7 +186,7 @@ SYSCALL_DEFINE5(kcmp, pid_t, pid1, pid_t, pid2, int, type,
ret = kcmp_ptr(task1->files, task2->files, KCMP_FILES);
break;
case KCMP_FS:
ret = kcmp_ptr(task1->fs, task2->fs, KCMP_FS);
ret = kcmp_ptr(task1->real_fs, task2->real_fs, KCMP_FS);
break;
case KCMP_SIGHAND:
ret = kcmp_ptr(task1->sighand, task2->sighand, KCMP_SIGHAND);