Skip to content

Commit 7601df8

Browse files
oleg-nesterovakpm00
authored andcommitted
fs/proc: do_task_stat: use sig->stats_lock to gather the threads/children stats
lock_task_sighand() can trigger a hard lockup. If NR_CPUS threads call do_task_stat() at the same time and the process has NR_THREADS, it will spin with irqs disabled O(NR_CPUS * NR_THREADS) time. Change do_task_stat() to use sig->stats_lock to gather the statistics outside of ->siglock protected section, in the likely case this code will run lockless. Link: https://lkml.kernel.org/r/20240123153357.GA21857@redhat.com Signed-off-by: Oleg Nesterov <oleg@redhat.com> Signed-off-by: Dylan Hatch <dylanbhatch@google.com> Cc: Eric W. Biederman <ebiederm@xmission.com> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
1 parent 60f92ac commit 7601df8

File tree

1 file changed

+32
-26
lines changed

1 file changed

+32
-26
lines changed

fs/proc/array.c

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -477,13 +477,13 @@ static int do_task_stat(struct seq_file *m, struct pid_namespace *ns,
477477
int permitted;
478478
struct mm_struct *mm;
479479
unsigned long long start_time;
480-
unsigned long cmin_flt = 0, cmaj_flt = 0;
481-
unsigned long min_flt = 0, maj_flt = 0;
482-
u64 cutime, cstime, utime, stime;
483-
u64 cgtime, gtime;
480+
unsigned long cmin_flt, cmaj_flt, min_flt, maj_flt;
481+
u64 cutime, cstime, cgtime, utime, stime, gtime;
484482
unsigned long rsslim = 0;
485483
unsigned long flags;
486484
int exit_code = task->exit_code;
485+
struct signal_struct *sig = task->signal;
486+
unsigned int seq = 1;
487487

488488
state = *get_task_state(task);
489489
vsize = eip = esp = 0;
@@ -511,12 +511,8 @@ static int do_task_stat(struct seq_file *m, struct pid_namespace *ns,
511511

512512
sigemptyset(&sigign);
513513
sigemptyset(&sigcatch);
514-
cutime = cstime = 0;
515-
cgtime = gtime = 0;
516514

517515
if (lock_task_sighand(task, &flags)) {
518-
struct signal_struct *sig = task->signal;
519-
520516
if (sig->tty) {
521517
struct pid *pgrp = tty_get_pgrp(sig->tty);
522518
tty_pgrp = pid_nr_ns(pgrp, ns);
@@ -527,27 +523,9 @@ static int do_task_stat(struct seq_file *m, struct pid_namespace *ns,
527523
num_threads = get_nr_threads(task);
528524
collect_sigign_sigcatch(task, &sigign, &sigcatch);
529525

530-
cmin_flt = sig->cmin_flt;
531-
cmaj_flt = sig->cmaj_flt;
532-
cutime = sig->cutime;
533-
cstime = sig->cstime;
534-
cgtime = sig->cgtime;
535526
rsslim = READ_ONCE(sig->rlim[RLIMIT_RSS].rlim_cur);
536527

537-
/* add up live thread stats at the group level */
538528
if (whole) {
539-
struct task_struct *t;
540-
541-
__for_each_thread(sig, t) {
542-
min_flt += t->min_flt;
543-
maj_flt += t->maj_flt;
544-
gtime += task_gtime(t);
545-
}
546-
547-
min_flt += sig->min_flt;
548-
maj_flt += sig->maj_flt;
549-
gtime += sig->gtime;
550-
551529
if (sig->flags & (SIGNAL_GROUP_EXIT | SIGNAL_STOP_STOPPED))
552530
exit_code = sig->group_exit_code;
553531
}
@@ -562,6 +540,34 @@ static int do_task_stat(struct seq_file *m, struct pid_namespace *ns,
562540
if (permitted && (!whole || num_threads < 2))
563541
wchan = !task_is_running(task);
564542

543+
do {
544+
seq++; /* 2 on the 1st/lockless path, otherwise odd */
545+
flags = read_seqbegin_or_lock_irqsave(&sig->stats_lock, &seq);
546+
547+
cmin_flt = sig->cmin_flt;
548+
cmaj_flt = sig->cmaj_flt;
549+
cutime = sig->cutime;
550+
cstime = sig->cstime;
551+
cgtime = sig->cgtime;
552+
553+
if (whole) {
554+
struct task_struct *t;
555+
556+
min_flt = sig->min_flt;
557+
maj_flt = sig->maj_flt;
558+
gtime = sig->gtime;
559+
560+
rcu_read_lock();
561+
__for_each_thread(sig, t) {
562+
min_flt += t->min_flt;
563+
maj_flt += t->maj_flt;
564+
gtime += task_gtime(t);
565+
}
566+
rcu_read_unlock();
567+
}
568+
} while (need_seqretry(&sig->stats_lock, seq));
569+
done_seqretry_irqrestore(&sig->stats_lock, seq, flags);
570+
565571
if (whole) {
566572
thread_group_cputime_adjusted(task, &utime, &stime);
567573
} else {

0 commit comments

Comments
 (0)