Page MenuHomeFreeBSD

dd(1): neutralize SIGINT while non-async-signal safe code is executing
ClosedPublic

Authored by kib on May 26 2023, 12:47 PM.
Tags
None
Referenced Files
Unknown Object (File)
Mar 15 2024, 11:58 AM
Unknown Object (File)
Mar 15 2024, 11:52 AM
Unknown Object (File)
Mar 13 2024, 1:45 AM
Unknown Object (File)
Mar 11 2024, 5:01 PM
Unknown Object (File)
Mar 11 2024, 5:01 PM
Unknown Object (File)
Mar 8 2024, 2:12 AM
Unknown Object (File)
Feb 22 2024, 8:58 AM
Unknown Object (File)
Feb 5 2024, 11:13 PM
Subscribers

Details

Summary
making the SIGINT handler (the terminate() function) safe to execute at
any interruption moment.  This fixes a race in
5807f35c541c26bbd91a3ae12506cd8dd8f20688 where SIGINT delivered right
after the check_terminate() but before a blocking syscall would not
cause abort.

Do it by setting the in_io flag around potentially blocking io syscalls.
If handler sees the flag, it terminates the program.  Otherwise,
termination is delegated to the before_io/after_io fences.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

kib requested review of this revision.May 26 2023, 12:47 PM

You are now blocking SIGINT whenever dd is not performing primary I/O. I don't think that's right.

In D40281#918424, @des wrote:

You are now blocking SIGINT whenever dd is not performing primary I/O. I don't think that's right.

How the effects of that are different than the current code, where the ^C handler just set a flag and returns? If my code is not right there, then the current unmodified code is not right either. I believe the correctness relies on the fact that dd performs a cpu-bound processing between i/o syscalls.

In D40281#918468, @kib wrote:

How the effects of that are different than the current code, where the ^C handler just set a flag and returns?

The one difference of any significance would be if the program is blocked on writing a progress output to stderr (e.g. if that has been directed to a pipe) when you ^C it; a handler installed without SA_RESTART that just sets a flag could nevertheless cause an EINTR from the write, whereas with signals blocked the program will remain blocked.

I don't know if anyone cares about this.

In D40281#918468, @kib wrote:

How the effects of that are different than the current code, where the ^C handler just set a flag and returns?

The one difference of any significance would be if the program is blocked on writing a progress output to stderr (e.g. if that has been directed to a pipe) when you ^C it; a handler installed without SA_RESTART that just sets a flag could nevertheless cause an EINTR from the write, whereas with signals blocked the program will remain blocked.

I don't know if anyone cares about this.

Indeed, I can fix this case as well, but IMO it is overkill. It should work by wrapping err()/warn*()/fprintf(stderr,...) with code that sets SIGINT handler to something that records the signal, and then unblocks it. It is esp. worthless because errors from stderr output are not handled.

I think I realized how this might be fixed more elegant, without compromising even the EINTR on stderr behavior. Instead of sys-blocking the signal, I can do it by exchanging two flags between the handler and fences. Updated patch follows.

kib retitled this revision from dd(1): block SIGINT while non-async-signal safe code is executing to dd(1): neutralize SIGINT while non-async-signal safe code is executing.
kib edited the summary of this revision. (Show Details)

Do not block SIGINT, use flags instead. This slightly resembles Peterson lock.

This is a massively over-engineered solution to a problem that doesn't need solving. I'm not sure why you're pursuing this. The problem I set out to solve has been solved and the remaining race is insignificant.

bin/dd/misc.c
195

Race here: if the signal delivers after the read of sigint_seen but before the write to in_io, the signal is not processed until after the I/O. This is basically no better than the plain flag version.

On the other hand, if you wrote in_io first, then a barrier, then check sigint_seen, then either the signal is delivered before the in_io write and so sigint_seen is set true, or it's delivered after and the remaining code is not reached because the handler exits.

kib marked an inline comment as done.Jun 1 2023, 7:35 PM
In D40281#919091, @des wrote:

This is a massively over-engineered solution to a problem that doesn't need solving. I'm not sure why you're pursuing this. The problem I set out to solve has been solved and the remaining race is insignificant.

What is over-engineered? The current solution is quite simple and solves the race. The race is the bug, whatever subtle it is, and I do care about it.

bin/dd/misc.c
195

You are right of course, thanks.

kib marked an inline comment as done.

Fix the order in before_io()

This revision is now accepted and ready to land.Jun 2 2023, 4:14 PM