Re: [PATCH v2 1/3] perf sched stats: Fix SIGCHLD race in schedstat_record()

Namhyung Kim <[email protected]>
Newsgroups dev.linux.lists.sashiko,org.kernel.vger.linux-perf-users
Message-ID <[email protected]>
On Thu, Apr 09, 2026 at 04:51:02PM +0000, [email protected] wrote:
> Sashiko AI review found 1 potential issue(s):
> - [Medium] The patch leaves a race window where early signals (like SIGINT) are lost, causing sigsuspend() to hang indefinitely.
> --
> 
> commit 82e42f3b8c8062e3b83019c07f66b6e7199c4104
> Author: Swapnil Sapkal <[email protected]>
> 
> perf sched stats: Fix SIGCHLD race in schedstat_record()
> 
> This commit attempts to fix a SIGCHLD race in perf sched stats record by
> replacing pause() with sigsuspend(). It blocks SIGCHLD, SIGINT, and SIGTERM
> before starting the workload to ensure no signal is lost if the child exits
> quickly.
> 
> > diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
> > --- a/tools/perf/builtin-sched.c
> > +++ b/tools/perf/builtin-sched.c
> 
> [ ... ]
> 
> > @@ -3899,11 +3900,25 @@ static int perf_sched__schedstat_record(struct perf_sched *sched,
> >  	if (err < 0)
> >  		goto out;
> >  
> > +	/*
> > +	 * Block all handled signals so that a short-lived workload exiting
> > +	 * (SIGCHLD) or an early Ctrl+C (SIGINT/SIGTERM) during the remaining
> > +	 * setup cannot be delivered before we are ready to wait. sigsuspend()
> > +	 * below will atomically unblock them. This is done after
> > +	 * evlist__prepare_workload() so the forked child does not inherit a
> > +	 * modified signal mask.
> > +	 */
> > +	sigemptyset(&sig_mask);
> > +	sigaddset(&sig_mask, SIGCHLD);
> > +	sigaddset(&sig_mask, SIGINT);
> > +	sigaddset(&sig_mask, SIGTERM);
> > +	sigprocmask(SIG_BLOCK, &sig_mask, &oldmask);
> 
> Does this leave a race window where early signals like SIGINT can be lost,
> causing sigsuspend() to hang indefinitely?
> 
> If a signal like SIGINT or SIGTERM is delivered before this sigprocmask()
> call, the existing sighandler will consume it without setting any state flag
> that gets checked here.
> 
> >  	if (argc)
> >  		evlist__start_workload(evlist);
> >  
> > -	/* wait for signal */
> > -	pause();
> > +	sigsuspend(&oldmask);
> 
> When execution subsequently reaches sigsuspend(), wouldn't it block
> indefinitely waiting for an event that was already processed?
> 
> To robustly resolve the TOCTOU race without leaking a modified signal mask
> to the child, should the signal handler set a volatile sig_atomic_t flag,
> and sigsuspend() be called conditionally based on that flag?

Right, maybe simply:

	if (argc) {
		evlist__start_workload(evlist);
		waitpid(evlist->workload.pid, NULL, 0);
	} else {
		while (!done)
			sleep(1);
	}

And SIG{INT,TERM,CHLD} should set done to true.

Thanks,
Namhyung

> 
> > +	sigprocmask(SIG_SETMASK, &oldmask, NULL);
> >  
> >  	if (reset) {
> >  		err = disable_sched_schedstat();
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.