Signal handling: what you can't do.

Geoff Keating geoffk at ozemail.com.au
Sun Nov 14 17:13:09 CET 1999


I just found this on gnupg-devel, to which I'm not subscribed.  I
don't remember replying to it, so...

Werner Koch wrote:
> Hi Geoff,
> 
> First, many thanks for your review.  I still have some questions:
> 
> Geoff Keating <geoffk at ozemail.com.au> writes:
> 
> > static RETSIGTYPE
> > got_fatal_signal( int sig )
> > {
> >     if( caught_fatal_sig )
> >         raise( sig );
> >     caught_fatal_sig = 1;
> > 
> >     fprintf( stderr, "\n%s: %s caught ... exiting\n",
> >               log_get_name(), signal_name(sig) );
> >     secmem_term();
> >     exit( 8 );
> > }
> > 
> > now, this is not good.  There are three issues here, but most flow
> > from the same important rule:
> > 
> > In a signal handler, under Linux (and most other unixes), you cannot:
> > 
> > 1. Assume that the values in non-volatile variables are stored in the same
> >    order as implied by the code (or in fact, that they are ever stored
> >    at all);
>
> But caught_fatal_sig is declared volatile; so where is the problem
> here?

The variables that secmem_term uses are not declared volatile.

> > 2. Call non-reentrant procedures.
> 
> I know that using printf is not good style but the only problem I can
> see, is that the output is garbled or you get an SEGV.

You may write to some important memory, which can be used like a
buffer overflow to gain root access (eg. a write to the PLT entry for
exit() ).

> I tried to use write(2) directly here but for some reasons
> (portability?) I gave up on this.  I assume that the correct solution
> would be to register an atexit function which displays such a message
> if a fatal signal occured.

You can't call 'exit', either.  You must call _exit.

Probably the right thing to do here is to just call raise(sig) again,
after clearing secure memory; eg.

static RETSIGTYPE
got_fatal_signal( int sig )
{
    if( ! caught_fatal_sig )
    {
      caught_fatal_sig = 1;
      secmem_term();
    }
    raise( sig );
}

> > Also for instance, in secmem_init, there is this code:
> > 
> >     else {
> >         pool_is_mmapped = 1;
> >         pool_okay = 1;
> >     }
> > 
> > there is absolutely no guarantee that `pool_is_mmapped' is physically
> > set to 1 before `pool_ok', or that `pool_ok' is set to 1 after the
> 
> Okay. I make pool_okay volatile.

You need to make both volatile.  If you have pool_okay volatile, the
will still move the pool_is_mmapped write after the change to
pool_okay.  You also need to make any other variable that
secmem_term may access volatile.

For instance, in the loop

extern volatile int foo;
extern int i;

void set_foo(void)
{
  for (i = 0; i < 10; i++)
    foo = 1;
}

you will get (assuming the optimization is still working) only one
write to 'i', at the end of the routine, but 10 writes to 'foo'.

> > Finally, a minor nit:  in signal_name(), there is this:
> > 
> >   #if SYS_SIGLIST_DECLARED
> >     return sys_siglist[signum];
> >   #else
> >     static char buf[20];
> >     sprintf(buf, "signal %d", signum );
> >     return buf;
> >   #endif
> > 
> > it should check that `signum' is less than NSIG, or better yet it
> > should use strsignal() which does the check for you and will still
> 
> Which is not available on BSD systems

This is why the GNU project has autoconf :-).

Better yet, remove signal_name altogether as I suggest above.

-- 
Geoffrey Keating <geoffk at cygnus.com>



More information about the Gnupg-devel mailing list