Re: [PATCH] ulogd: Restructures signal handling by self-pipe trick.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello,

On Tue, 2016-02-02 at 23:01 +0900, Hironobu Ishii wrote:
> ulogd had a critical bug that is calling Async-Signal-Unsafe
> functions
> in signal hander context.

Thanks a lot for this detailed bug report and fix. I'm gonna apply it
with minor white space fixes.

BR,
--
Eric.


>   - Most of libc functions like fopen(), malloc() are Async-Signal-
> Unsafe.
>     So you should not call these functions in signal handler context.
>   - Calling pluginstances in signal handler context is danger.
>     For implementer of pluginstances, it is very hard to recognize
> their
>     functions are called in signal handler context.
> 
> To solve the issue, I restructured signal handling by self-pipe
> trick.
> For more detail on self-pipe trick, please see the following.
> https://lwn.net/Articles/177897/
> 
> This patch will solve various symptoms like following.
>   - Deadlock
>   - Segmentation fault caused by libc management data corruption,
>   - Other unpredictable behavior.
> 
> Deadlock example
> ================
> This bug was already filed at:
> https://bugzilla.netfilter.org/show_bug.cgi?id=1030
> 
> I also hit this bug. The backtrace of this issue is following.
> In this case, main thread was calling ctime(),
> and signal handler called localtime_r().
> That caused the dead lock while getting tzset_lock in __tz_convert().
> Because vsyslog() is Async-Signal-Unsafe function, we cannot call
> this function in signal handler context.
> 
>  (gdb) bt
>  #0  __lll_lock_wait_private () at
> ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:95
>  #1  0x00007f3c3fc7e4ac in _L_lock_2462 () at tzset.c:621
>  #2  0x00007f3c3fc7e2e7 in __tz_convert (timer=0x7f3c3ff8bf00
> <tzset_lock>,
>      timer@entry=0x7fffcfa923b8, use_localtime=use_localtime@entry=1,
>      tp=tp@entry=0x7fffcfa92400) at tzset.c:624
>  #3  0x00007f3c3fc7c28d in __localtime_r (t=t@entry=0x7fffcfa923b8,
>      tp=tp@entry=0x7fffcfa92400) at localtime.c:32
>  #4  0x00007f3c3fcbf1ba in __GI___vsyslog_chk (pri=<optimized out>,
> flag=1,
>      fmt=0x406fa8 "signal received, calling pluginstances\n",
> ap=0x7fffcfa924a0)
>      at ../misc/syslog.c:199
>  #5  0x00000000004037b5 in __ulogd_log ()
>  #6  0x00000000004047be in signal_handler ()
>  #7  <signal handler called>
>  #8  0x00007f3c3fcb62f5 in __GI___xstat (vers=<optimized out>,
>      name=0x7f3c3fd4b2c3 "/etc/localtime", buf=0x7fffcfa92c10)
>      at ../sysdeps/unix/sysv/linux/wordsize-64/xstat.c:37
>  #9  0x00007f3c3fc7e5f6 in __tzfile_read (file=file@entry=0x7f3c3fd4b
> 2c3 "/etc/localtime",
>      extra=extra@entry=0, extrap=extrap@entry=0x0) at tzfile.c:170
>  #10 0x00007f3c3fc7d954 in tzset_internal (always=<optimized out>,
>      explicit=explicit@entry=1) at tzset.c:444
>  #11 0x00007f3c3fc7e303 in __tz_convert (timer=0x7fffcfa92d50,
>      use_localtime=use_localtime@entry=1, tp=tp@entry=0x7f3c3ff8ed80
> <_tmbuf>)
>      at tzset.c:629
>  #12 0x00007f3c3fc7c2a1 in __GI_localtime (t=<optimized out>) at
> localtime.c:42
>  #13 0x00007f3c3fc7c1f9 in ctime (t=<optimized out>) at ctime.c:27
>  #14 0x00007f3c3e180ec2 in ?? ()
>  #15 0x0000000056a100c2 in ?? ()
>  #16 0xf8570f79d4fc4200 in ?? ()
>  #17 0x000000000209bec0 in ?? ()
>  #18 0x00007f3c4059f1f8 in ?? ()
>  #19 0x000000000000003c in ?? ()
>  #20 0x0000000000404952 in ulogd_propagate_results ()
>  #21 0x00007f3c3f9cc203 in ?? ()
>  #22 0x0000000000000000 in ?? ()
> 
> Segmentation fault in free()
> ============================
> From my experience, I think this was caused by some routine called
> malloc()/free() in signal handler context.
> By that, malloc() management data became inconsistent.
> As a result, free() made a wrong dereference.
> 
> Program terminated with signal SIGSEGV, Segmentation fault.
>  #0  __GI___libc_free (mem=0x7f430f011000) at malloc.c:2903
>  2903      if (chunk_is_mmapped(p))                       /* release
> mmapped memory. */
>  (gdb) bt
>  #0  __GI___libc_free (mem=0x7f430f011000) at malloc.c:2903
>  #1  0x00007f430e68affa in __GI__IO_free_backup_area (fp=fp@entry=0x7
> 42500)
>      at genops.c:210
>  #2  0x00007f430e68a795 in _IO_new_file_overflow (f=0x742500, ch=-1)
> at fileops.c:849
>  #3  0x00007f430e689511 in _IO_new_file_xsputn (f=0x742500,
> data=<optimized out>, n=15)
>      at fileops.c:1372
>  #4  0x00007f430e65aa4d in _IO_vfprintf_internal (s=s@entry=0x742500,
>      format=<optimized out>, format@entry=0x7f430cbc4008 "%.15s %s
> %s",
>      ap=ap@entry=0x7fff456ece38) at vfprintf.c:1635
>  #5  0x00007f430e71d615 in ___fprintf_chk (fp=0x742500, flag=flag@ent
> ry=1,
>      format=format@entry=0x7f430cbc4008 "%.15s %s %s") at
> fprintf_chk.c:36
>  #6  0x00007f430cbc3f04 in fprintf (__fmt=0x7f430cbc4008 "%.15s %s
> %s",
>      __stream=<optimized out>) at /usr/include/bits/stdio2.h:97
>  #7  _output_logemu (upi=0x74e5a0) at ulogd_output_LOGEMU.c:102
>  #8  0x0000000000404952 in ulogd_propagate_results ()
>  #9  0x00007f430e40f203 in interp_packet (ldata=0x7fff456ed060,
> pf_family=2 '\002',
>      upi=0x74a6b0) at ulogd_inppkt_NFLOG.c:400
>  #10 msg_cb (gh=<optimized out>, nfmsg=0x7f430efe2020,
> nfa=0x7fff456ed060, data=0x74a6b0)
>      at ulogd_inppkt_NFLOG.c:483
>  #11 0x00007f430e20a307 in __nflog_rcv_pkt (nlh=<optimized out>,
> nfa=<optimized out>,
>      data=<optimized out>) at libnetfilter_log.c:160
>  #12 0x00007f430e0056b7 in __nfnl_handle_msg (len=268,
> nlh=0x7f430efe2010, h=0x74e8e0)
>      at libnfnetlink.c:1236
>  #13 nfnl_handle_packet (h=0x74e8e0, buf=0x7f430efe2010 "\f\001",
> len=<optimized out>)
>      at libnfnetlink.c:1256
>  #14 0x00007f430e20a508 in nflog_handle_packet (h=<optimized out>,
> buf=<optimized out>,
>      len=<optimized out>) at libnetfilter_log.c:323
>  #15 0x00007f430e40eaed in nful_read_cb (fd=<optimized out>,
> what=<optimized out>,
>      param=0x74a6b0) at ulogd_inppkt_NFLOG.c:463
>  #16 0x0000000000404ee0 in ulogd_select_main ()
>  #17 0x0000000000402b17 in main ()
> 
> Signed-off-by: Hironobu Ishii <ishii.hironobu@xxxxxxxxxxxxxx>
> ---
>  src/ulogd.c | 132
> ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
>  1 file changed, 125 insertions(+), 7 deletions(-)
> 
> diff --git a/src/ulogd.c b/src/ulogd.c
> index 958c30a..4c76e43 100644
> --- a/src/ulogd.c
> +++ b/src/ulogd.c
> @@ -90,6 +90,29 @@ static int info_mode = 0;
>  static int verbose = 0;
>  static int created_pidfile = 0;
>  
> +/*
> + * Using self-pipe trick to handle signals safely.
> + * (To avoid calling Async-Signal-Unsafe functions in signal
> handler.)
> + *
> + * cf. https://lwn.net/Articles/177897/
> + * 
> + * Capacity of pipe(2) buffer is >= 4096.
> + */
> +static int signal_channel[2] = { -1, -1 };	/* initialize with
> invalid fd */
> +#define	SIGNAL_RX_FD	(signal_channel[0])	/*
> main thread select() this fd */
> +#define	SIGNAL_TX_FD	(signal_channel[1])	/*
> signal handler writes to this fd */
> +
> +static void signal_handler(int signal);
> +static void sigterm_handler_task(int signal);
> +static void signal_handler_task(int signal);
> +
> +static int  signal_channel_callback(int fd, unsigned int what, void
> *data);
> +static struct ulogd_fd	signal_channel_ulogfd = {
> +	.when = ULOGD_FD_READ,
> +	.cb = &signal_channel_callback,
> +	.data = &signal_channel_ulogfd,
> +};
> +
>  /* linked list for all registered plugins */
>  static LLIST_HEAD(ulogd_plugins);
>  /* linked list for all plugins handle */
> @@ -387,6 +410,80 @@ void ulogd_register_plugin(struct ulogd_plugin
> *me)
>  }
>  
>  /*******************************************************************
> ****
> + * UTILITY FUNCTIONS FOR SIGNAL HANDLING
> +
> *********************************************************************
> **/
> +static void call_signal_handler_tasks(int sig)
> +{
> +	/* Deliver signals */
> +	switch (sig) {
> +	case SIGTERM:
> +	case SIGINT:
> +		sigterm_handler_task(sig);
> +		break;
> +	case SIGHUP:
> +	case SIGALRM:
> +	case SIGUSR1:
> +	case SIGUSR2:
> +		signal_handler_task(sig);
> +		break;
> +	default:
> +		break;
> +	}
> +}
> +
> +static int signal_channel_callback(int fd, unsigned int what, void
> *data)
> +{
> +	int	ret;
> +	unsigned char c;
> +
> +	while ((ret = read(fd, &c, 1)) > 0) {
> +		call_signal_handler_tasks((int)c);
> +	}
> +	return 0;
> +}
> +
> +static int create_signal_channel(void)
> +{
> +	int	ret;
> +	long	flags;
> +
> +	if ((ret = pipe(signal_channel)) < 0)
> +		return ret;
> +
> +	/* SIGNAL_RX_FD, channel[0] */
> +	if ((flags = fcntl(SIGNAL_RX_FD, F_GETFL)) < 0)
> +		goto err_exit;
> +	flags |= O_NONBLOCK;
> +	if ( fcntl(SIGNAL_RX_FD, F_SETFL, flags) < 0 )
> +		goto err_exit;
> +
> +	/* SIGNAL_TX_FD, channel[1] */
> +	if ((flags = fcntl(SIGNAL_TX_FD, F_GETFL)) < 0)
> +		goto err_exit;
> +	flags |= O_NONBLOCK;
> +	if ( fcntl(SIGNAL_TX_FD, F_SETFL, flags) < 0 )
> +		goto err_exit;
> +
> +	/* register SIGNAL_RX_FD to ulogd */
> +	signal_channel_ulogfd.fd = SIGNAL_RX_FD;
> +	ulogd_register_fd(&signal_channel_ulogfd);
> +
> +	return 0;
> +
> +err_exit:
> +	if (SIGNAL_RX_FD >= 0) {
> +		(void)close(SIGNAL_RX_FD);
> +		SIGNAL_RX_FD = -1;
> +	}
> +	if (SIGNAL_TX_FD >= 0) {
> +		(void)close(SIGNAL_TX_FD);
> +		SIGNAL_TX_FD = -1;
> +	}
> +	return -1;
> +}
> +
> +
> +/*******************************************************************
> ****
>   * MAIN PROGRAM
>  
> *********************************************************************
> **/
>  
> @@ -925,7 +1022,6 @@ static void ulogd_main_loop(void)
>  	struct timeval *next = NULL;
>  
>  	while (1) {
> -		/* XXX: signal blocking? */
>  		if (next != NULL && !timerisset(next))
>  			next = ulogd_do_timer_run(&next_alarm);
>  		else
> @@ -1225,7 +1321,19 @@ static void stop_stack()
>  }
>  
>  
> -static void sigterm_handler(int signal)
> +/* This is a real SIGTERM, SIGINT, SIGHUP, SIGALRM, SIGUSR1, SIGUSR2
> handler */
> +static void signal_handler(int signal)
> +{
> +	unsigned char	c = (unsigned char)signal;
> +
> +	(void)write(SIGNAL_TX_FD, &c, 1);
> +}
> +
> +/* This is NOT a real signal handler.
> + * It is called in ulogd_main_loop() to avoid
> + * calling Async-Signal-UnSafe functions in signal handler.
> + */
> +static void sigterm_handler_task(int signal)
>  {
>  
>  	ulogd_log(ULOGD_NOTICE, "Terminal signal received,
> exiting\n");
> @@ -1255,7 +1363,12 @@ static void sigterm_handler(int signal)
>  	exit(0);
>  }
>  
> -static void signal_handler(int signal)
> +
> +/* This is NOT a real signal handler.
> + * It is called in ulogd_main_loop() to avoid
> + * calling Async-Signal-UnSafe functions in signal handler.
> + */
> +static void signal_handler_task(int signal)
>  {
>  	ulogd_log(ULOGD_NOTICE, "signal received, calling
> pluginstances\n");
>  	
> @@ -1269,7 +1382,7 @@ static void signal_handler(int signal)
>  				fprintf(stderr, 
>  					"ERROR: can't open logfile
> %s: %s\n", 
>  					ulogd_logfile,
> strerror(errno));
> -				sigterm_handler(signal);
> +				sigterm_handler_task(signal);
>  			}
>  	
>  		}
> @@ -1464,8 +1577,13 @@ int main(int argc, char* argv[])
>  		}
>  	}
>  
> -	signal(SIGTERM, &sigterm_handler);
> -	signal(SIGINT, &sigterm_handler);
> +	if (create_signal_channel() < 0) {
> +		ulogd_log(ULOGD_FATAL, "can't create signal
> channel\n");
> +		warn_and_exit(daemonize);
> +	}
> +
> +	signal(SIGTERM, &signal_handler);
> +	signal(SIGINT, &signal_handler);
>  	signal(SIGHUP, &signal_handler);
>  	signal(SIGALRM, &signal_handler);
>  	signal(SIGUSR1, &signal_handler);
> @@ -1477,6 +1595,6 @@ int main(int argc, char* argv[])
>  	ulogd_main_loop();
>  
>  	/* hackish, but result is the same */
> -	sigterm_handler(SIGTERM);	
> +	sigterm_handler_task(SIGTERM);	
>  	return(0);
>  }
-- 
Eric Leblond <eric@xxxxxxxxx>


--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Netfitler Users]     [LARTC]     [Bugtraq]     [Yosemite Forum]

  Powered by Linux