invoking a segfault within a segfault handler - is this undefinedbehavior?

A

Andrey Vul

In the following code, the dereferencing of NULL at line main+2
triggers a segfault, which calls handl(SEGV). Is the behavior
dereferencing of NULL in the SEGV handler undefined, implementation-
dependent, or standardized?
It looks like the OS doesn't want to tolerate double pagefaults or
nested fatal signals, and simply kills the offending process, which
lead me to reading ISO 9899:1999.
I couldn't find anything to do with nested terminate-or-core signals
in the spec. Could I get clarification on this?

Note: raise(u) instead of dereferencing NULL did not cause a crash.

code:

#include <stdio.h>
#include <signal.h>
sig_atomic_t dep;
void handl(int u) {
dep++;
write(1, &dep, sizeof dep);
*((char *)0) = 0;
}
int main() {
dep=0;
signal(SIGSEGV, handl);
*((char *)0) = 0;
return 0;
}
 
A

Andrey Vul

Basically, I'm asking for clarification wrt 7.14.1.1.3, specifically
for computational-exception within a computational-exception handler.
 
B

Ben Bacarisse

Andrey Vul said:
Basically, I'm asking for clarification wrt 7.14.1.1.3, specifically
for computational-exception within a computational-exception handler.

I'll annotate the text as best I can:

When a signal occurs and func points to a function,

This is awkward wording but I think it is clear. It is describing what
happens when a signal occurs after the signal's action has been set by
calling signal with a function pointer argument.

it is implementation-defined whether the equivalent of signal(sig,
SIG_DFL); is executed or the implementation prevents some
implementation-defined set of signals (at least including sig) from
occurring until the current signal handling has completed;

Implementation defined means that the implementation must document what
choice it makes between some set of alternatives. So either:

(a) the signal's action is reset to the default, or
(b) some set of signals is blocked. This set must be specified by the
documentation and is must include the signal being handled.

Your implementation is clearly doing (a).

in the case of SIGILL, the
implementation may alternatively define that no action is taken.

No relevant but clear enough, I think.

Then the equivalent of (*func)(sig); is executed.

So the above happens before the handler is called.

If and when the function returns, if the value of sig is SIGFPE,
SIGILL, SIGSEGV, or any other implementation-defined value
corresponding to a computational exception, the behavior is undefined;
otherwise the program will resume execution at the point it was
interrupted.

The behaviour is undefined when the function returns. You are handling
SIGSEGV which is one of the named computational exceptions (the
implementation can specify others).

Does that help? It would help to know what you want to do and how
portable you want it to be.
 
A

Andrey Vul

I'll annotate the text as best I can:

  When a signal occurs and func points to a function,

This is awkward wording but I think it is clear.  It is describing what
happens when a signal occurs after the signal's action has been set by
calling signal with a function pointer argument.

  it is implementation-defined whether the equivalent of signal(sig,
  SIG_DFL); is executed or the implementation prevents some
  implementation-defined set of signals (at least including sig) from
  occurring until the current signal handling has completed;

Implementation defined means that the implementation must document what
choice it makes between some set of alternatives.  So either:

(a) the signal's action is reset to the default, or
(b) some set of signals is blocked.  This set must be specified by the
documentation and is must include the signal being handled.

Your implementation is clearly doing (a).

  in the case of SIGILL, the
  implementation may alternatively define that no action is taken.

No relevant but clear enough, I think.

  Then the equivalent of (*func)(sig); is executed.

So the above happens before the handler is called.

  If and when the function returns, if the value of sig is SIGFPE,
  SIGILL, SIGSEGV, or any other implementation-defined value
  corresponding to a computational exception, the behavior is undefined;
  otherwise the program will resume execution at the point it was
  interrupted.
The behaviour is undefined when the function returns.  You are handling
SIGSEGV which is one of the named computational exceptions (the
implementation can specify others).

Does that help?  It would help to know what you want to do and how
portable you want it to be.

What I was trying to do was force /sbin/init to abnormally terminate,
causing system lockup.
I got it work work with a double segfault, but was asking if this
method caused undefined behavior.
 
B

Ben Bacarisse

Andrey Vul said:
What I was trying to do was force /sbin/init to abnormally terminate,
causing system lockup.
I got it work work with a double segfault, but was asking if this
method caused undefined behavior.

I don't think so, but it would have been simpler to call abort or _Exit,
no? I'm not sure what you can and can't change in this scenario, but if
you added the original seg faulting code rather than simple exploiting
an existing seg fault, you could have just put an abort or _Exit call in
the there instead. If you can't do that, abort or _Exit from the signal
handler would have done, I think.
 
A

Andrey Vul

I don't think so, but it would have been simpler to call abort or _Exit,
no?  I'm not sure what you can and can't change in this scenario, but if
you added the original seg faulting code rather than simple exploiting
an existing seg fault, you could have just put an abort or _Exit call in
the there instead.  If you can't do that, abort or _Exit from the signal
handler would have done, I think.

abort() in the signal handler only kills the program from infinite
recursion, as init's signal handler traps SIGABRT.
I could've simply added a segfault in the handler, and do a #kill -11
1 , but making a runlevel that triggers a double segfault was more fun
to do :)
However, _Exit without forced crash would've saved a few lines of
code. Let me try that after a reboot.
 
A

Andrey Vul

Methinks signal(SIGSEGV, SIG_DFL); raise(SIGSEGV) would be even
simpler.
After all, it'd be more subtle than _Exit(SIGSEGV).
 
N

Nobody

Methinks signal(SIGSEGV, SIG_DFL); raise(SIGSEGV) would be even
simpler.
After all, it'd be more subtle than _Exit(SIGSEGV).

Note that Linux treats init specially; the kill(2) manpage says:

NOTES
The only signals that can be sent to process ID 1, the init process,
are those for which init has explicitly installed signal handlers.
This is done to assure the system is not brought down accidentally.
 
R

Richard Bos

Andrey Vul said:
In the following code, the dereferencing of NULL at line main+2
triggers a segfault, which calls handl(SEGV). Is the behavior
dereferencing of NULL in the SEGV handler undefined, implementation-
dependent, or standardized?

7.14.1.1#3:
# When a signal occurs and func points to a function, it is
# implementation-defined whether the equivalent of signal(sig, SIG_DFL);
# is executed or the implementation prevents some implementation-defined
# set of signals (at least including sig) from occurring until the
# current signal handling has completed;

IOW, it's Standardised that a programmer-defined signal handler can't
raise _itself_ recursively[1]. Whether it can raise the default handler
instead is implementation-defined.
C89 has text that is similar enough to mean the same thing for your
program.
#include <stdio.h>
#include <signal.h>
sig_atomic_t dep;
void handl(int u) {
dep++;
write(1, &dep, sizeof dep);
*((char *)0) = 0;
}
int main() {
dep=0;
signal(SIGSEGV, handl);
*((char *)0) = 0;
return 0;
}

Richard

[1] Not _strictly_ true; but true in this case. You might be able to do
it (if the implementation allows you to) by setting handl() for two
different signals, and then raising one of those in main(), and
another in handl(). Of course, it's quite possible that the "set of
signals" referred to in the quotation include, for your compiler,
_both_ signals you set for handl()... but not required.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Staff online

Members online

Forum statistics

Threads
473,731
Messages
2,569,432
Members
44,832
Latest member
GlennSmall

Latest Threads

Top