Avoid crash by SIGFPE during plural form expression evaluation.

Libraries (such as libintl and libgettextpo) should not raise signals, because
catching such signals cannot be done in a multithread-safe way.

* gettext-runtime/intl/plural-exp.h (enum eval_status): New enum items
PE_INTDIV, PE_INTOVF.
* gettext-runtime/intl/eval-plural.h (plural_eval_recurse): When a division
argument is zero, return status PE_INTDIV instead of raising SIGFPE.
* gettext-runtime/intl/dcigettext.c (INTDIV0_RAISES_SIGFPE): Remove macro.
Don't include <signal.h>.
* gettext-runtime/intl/configure.ac: Don't invoke gt_INTDIV0.
* gettext-runtime/intl/m4/intdiv0.m4: Remove file.
* gettext-runtime/intl/Makefile.am (ACLOCAL_AMFLAGS): Remove option '-I m4'.
(EXTRA_DIST): Remove m4/intdiv0.m4.
* autogen.sh: When invoking aclocal in gettext-runtime/intl, omit option
'-I m4'.

* gettext-tools/src/msgl-check.c: Don't include <setjmp.h>, <signal.h>.
(plural_expression_histogram): Don't invoke install_sigfpe_handler,
uninstall_sigfpe_handler.
(check_plural_eval): Don't invoke setjmp, install_sigfpe_handler,
uninstall_sigfpe_handler. Remove 'volatile' from array variable.
Produce error message when status PE_INTDIV or PE_INTOVF occurred.
* gettext-tools/src/plural-eval.h: Don't include <setjmp.h>.
(sigjmp_buf, sigsetjmp, siglongjmp): Remove definitions.
(USE_SIGINFO): Remove macro.
(sigfpe_exit, sigfpe_code, install_sigfpe_handler, uninstall_sigfpe_handler):
Remove declarations.
* gettext-tools/src/plural-eval.c: Don't include <signal.h>.
(sigfpe_exit, sigfpe_code): Remove variables.
(sigfpe_handler, install_sigfpe_handler, uninstall_sigfpe_handler): Remove
functions.
* gettext-tools/configure.ac: Don't invoke gt_SIGINFO.
* gettext-tools/m4/siginfo.m4: Remove file.
* gettext-tools/m4/Makefile.am (EXTRA_DIST): Remove it.
This commit is contained in:
Bruno Haible 2023-10-01 20:18:23 +02:00
parent 429ba6c6b8
commit 726bfb1d1c
13 changed files with 60 additions and 383 deletions

View File

@ -438,7 +438,7 @@ dir0=`pwd`
echo "$0: generating configure in gettext-runtime/intl..."
cd gettext-runtime/intl
aclocal -I ../../m4 -I ../m4 -I m4 -I gnulib-m4 \
aclocal -I ../../m4 -I ../m4 -I gnulib-m4 \
&& autoconf \
&& autoheader && touch config.h.in \
&& touch ChangeLog \

View File

@ -18,12 +18,11 @@
## Process this file with automake to produce Makefile.in.
AUTOMAKE_OPTIONS = 1.10 gnu no-dependencies
ACLOCAL_AMFLAGS = -I ../../m4 -I ../m4 -I m4 -I gnulib-m4
ACLOCAL_AMFLAGS = -I ../../m4 -I ../m4 -I gnulib-m4
SUBDIRS = gnulib-lib
EXTRA_DIST = \
m4/intdiv0.m4
EXTRA_DIST =
BUILT_SOURCES =
MOSTLYCLEANFILES = core *.stackdump
CLEANFILES =

View File

@ -118,7 +118,6 @@ AC_C_INLINE
AC_TYPE_SIZE_T
AC_FUNC_ALLOCA
AC_FUNC_MMAP
gt_INTDIV0
gl_LOCK
AC_LINK_IFELSE(

View File

@ -69,20 +69,6 @@ extern int errno;
#include <locale.h>
#ifdef _LIBC
/* Guess whether integer division by zero raises signal SIGFPE.
Set to 1 only if you know for sure. In case of doubt, set to 0. */
# if defined __alpha__ || defined __arm__ || defined __i386__ \
|| defined __m68k__ || defined __s390__
# define INTDIV0_RAISES_SIGFPE 1
# else
# define INTDIV0_RAISES_SIGFPE 0
# endif
#endif
#if !INTDIV0_RAISES_SIGFPE
# include <signal.h>
#endif
#if defined HAVE_SYS_PARAM_H || defined _LIBC
# include <sys/param.h>
#endif

View File

@ -117,16 +117,12 @@ plural_eval_recurse (const struct expression *pexp, unsigned long int n,
case mult:
return OK (leftarg.value * rightarg.value);
case divide:
#if !INTDIV0_RAISES_SIGFPE
if (rightarg.value == 0)
raise (SIGFPE);
#endif
return (struct eval_result) { .status = PE_INTDIV };
return OK (leftarg.value / rightarg.value);
case module:
#if !INTDIV0_RAISES_SIGFPE
if (rightarg.value == 0)
raise (SIGFPE);
#endif
return (struct eval_result) { .status = PE_INTDIV };
return OK (leftarg.value % rightarg.value);
case plus:
return OK (leftarg.value + rightarg.value);

View File

@ -1,90 +0,0 @@
# intdiv0.m4 serial 9 (gettext-0.21.1)
dnl Copyright (C) 2002, 2007-2008, 2010-2020 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.
dnl From Bruno Haible.
AC_DEFUN([gt_INTDIV0],
[
AC_REQUIRE([AC_PROG_CC])dnl
AC_REQUIRE([AC_CANONICAL_HOST])dnl
AC_CACHE_CHECK([whether integer division by zero raises SIGFPE],
[gt_cv_int_divbyzero_sigfpe],
[
gt_cv_int_divbyzero_sigfpe=
changequote(,)dnl
case "$host_os" in
macos* | darwin[6-9]* | darwin[1-9][0-9]*)
# On Mac OS X 10.2 or newer, just assume the same as when cross-
# compiling. If we were to perform the real test, 1 Crash Report
# dialog window would pop up.
case "$host_cpu" in
i[34567]86 | x86_64)
gt_cv_int_divbyzero_sigfpe="guessing yes" ;;
esac
;;
esac
changequote([,])dnl
if test -z "$gt_cv_int_divbyzero_sigfpe"; then
AC_RUN_IFELSE(
[AC_LANG_SOURCE([[
#include <stdlib.h> /* for exit() */
#include <signal.h>
#if !(defined _WIN32 && !defined __CYGWIN__)
#include <unistd.h> /* for _exit() */
#endif
static void
sigfpe_handler (int sig)
{
/* Exit with code 0 if SIGFPE, with code 1 if any other signal. */
_exit (sig != SIGFPE);
}
int x = 1;
int y = 0;
int z;
int inan;
int main ()
{
signal (SIGFPE, sigfpe_handler);
/* IRIX and AIX (when "xlc -qcheck" is used) yield signal SIGTRAP. */
#if (defined (__sgi) || defined (_AIX)) && defined (SIGTRAP)
signal (SIGTRAP, sigfpe_handler);
#endif
/* Linux/SPARC yields signal SIGILL. */
#if defined (__sparc__) && defined (__linux__)
signal (SIGILL, sigfpe_handler);
#endif
z = x / y;
inan = y / y;
exit (2);
}
]])],
[gt_cv_int_divbyzero_sigfpe=yes],
[gt_cv_int_divbyzero_sigfpe=no],
[
# Guess based on the CPU.
changequote(,)dnl
case "$host_cpu" in
alpha* | i[34567]86 | x86_64 | m68k | s390*)
gt_cv_int_divbyzero_sigfpe="guessing yes";;
*)
gt_cv_int_divbyzero_sigfpe="guessing no";;
esac
changequote([,])dnl
])
fi
])
case "$gt_cv_int_divbyzero_sigfpe" in
*yes) value=1 ;;
*) value=0 ;;
esac
AC_DEFINE_UNQUOTED([INTDIV0_RAISES_SIGFPE], [$value],
[Define if integer division by zero raises signal SIGFPE.])
])

View File

@ -117,6 +117,8 @@ extern void EXTRACT_PLURAL_EXPRESSION (const char *nullentry,
enum eval_status
{
PE_OK, /* Evaluation succeeded, produced a value */
PE_INTDIV, /* Integer division by zero */
PE_INTOVF, /* Integer overflow */
PE_STACKOVF, /* Stack overflow */
PE_ASSERT /* Assertion failure */
};

View File

@ -231,7 +231,6 @@ AC_TYPE_PID_T
dnl Checks for library functions.
AC_CHECK_FUNCS([select])
gt_SIGINFO
AC_C_BIGENDIAN([endianness=1], [endianness=0],
[echo "AC-C-BIGENDIAN fails to work on your system." | sed -e 's,-,_,g' 1>&2

View File

@ -46,5 +46,4 @@ EXTRA_DIST = \
csharpexec-test.cs csharpexec-test.exe \
exported.m4 \
hostname.m4 \
locale-de.m4 \
siginfo.m4
locale-de.m4

View File

@ -1,53 +0,0 @@
# siginfo.m4 serial 2 (gettext-0.21.1)
dnl Copyright (C) 2001-2002, 2020 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.
# Determine how to determine the precise cause of a signal, for example
# division by zero.
# - SUSV2 and POSIX specify the use of sigaction with SA_SIGINFO and a member
# void (*)(int sig, siginfo_t *info, void *context) sa_sigaction.
# Linux (2.2.x and newer) and Solaris implement this.
# Linux (2.4.x and newer) on i386, m68k, sparc, sparc64, ia64 actually
# deliver FPE_INTDIV.
# - Without SA_SIGINFO:
# - Linux on m68k calls the handler as
# void (*)(int sig, int code, struct sigcontext* scp).
# For division by zero, code would be VEC_ZERODIV<<2.
# - Linux on sparc calls the handler either as
# void (*)(int sig, int code, struct sigcontext* scp),
# code for division by zero would be SUBSIG_IDIVZERO, or as
# void (*)(int sig, siginfo_t *info, void *context).
# Which one depends on a process specific flag in the kernel.
# - Linux on sparc64 always calls the handler as
# void (*)(int sig, siginfo_t *info, void *context).
# - FreeBSD on i386 calls the handler as
# void (*)(int sig, int code, void* scp, char* addr).
# For division by zero, code would be FPE_INTDIV.
# - SunOS 4 calls the handler as
# void (*)(int sig, int code, void* scp, char* addr).
# - Solaris?
# - Irix 5, OSF/1, AIX call the handler as
# void (*)(int sig, int code, struct sigcontext *scp).
# These are so many OS and CPU dependencies that we don't bother, and rely
# only on SA_SIGINFO.
AC_DEFUN([gt_SIGINFO],
[
AC_CACHE_CHECK([for signal handlers with siginfo_t], gt_cv_siginfo_t,
[AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM(
[[#include <signal.h>]],
[[struct sigaction action;
siginfo_t info;
action.sa_flags = SA_SIGINFO;
action.sa_sigaction = (void *) 0;
]])],
[gt_cv_siginfo_t=yes],
[gt_cv_siginfo_t=no])])
if test $gt_cv_siginfo_t = yes; then
AC_DEFINE([HAVE_SIGINFO], [1],
[Define to 1 if <signal.h> defines the siginfo_t type and
struct sigaction has the sa_sigaction member and the SA_SIGINFO flag.])
fi
])

View File

@ -23,8 +23,6 @@
#include "msgl-check.h"
#include <limits.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -69,9 +67,6 @@ plural_expression_histogram (const struct plural_distribution *self,
unsigned long n;
unsigned int count;
/* Protect against arithmetic exceptions. */
install_sigfpe_handler ();
count = 0;
for (n = min; n <= max; n++)
{
@ -81,9 +76,6 @@ plural_expression_histogram (const struct plural_distribution *self,
count++;
}
/* End of protection against arithmetic exceptions. */
uninstall_sigfpe_handler ();
return count;
}
else
@ -105,7 +97,7 @@ check_plural_eval (const struct expression *plural_expr,
/* Do as if the plural formula assumes a value N infinitely often if it
assumes it at least 5 times. */
#define OFTEN 5
unsigned char * volatile array;
unsigned char *array;
/* Allocate a distribution array. */
if (nplurals_value <= 100)
@ -114,116 +106,69 @@ check_plural_eval (const struct expression *plural_expr,
/* nplurals_value is nonsense. Don't risk an out-of-memory. */
array = NULL;
if (sigsetjmp (sigfpe_exit, 1) == 0)
unsigned long n;
for (n = 0; n <= 1000; n++)
{
unsigned long n;
/* Protect against arithmetic exceptions. */
install_sigfpe_handler ();
for (n = 0; n <= 1000; n++)
struct eval_result res = plural_eval (plural_expr, n);
if (res.status != PE_OK)
{
struct eval_result res = plural_eval (plural_expr, n);
if (res.status != PE_OK)
{
/* End of protection against arithmetic exceptions. */
uninstall_sigfpe_handler ();
if (res.status == PE_INTDIV)
po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false,
_("plural expression can produce division by zero"));
else if (res.status == PE_INTOVF)
po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false,
_("plural expression can produce integer overflow"));
else if (res.status == PE_STACKOVF)
po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false,
_("plural expression can produce stack overflow"));
else
/* Other res.status values should not occur. */
abort ();
if (res.status == PE_STACKOVF)
po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false,
_("plural expression can produce stack overflow"));
else
/* Other res.status values should not occur. */
abort ();
free (array);
return 1;
}
unsigned long val = res.value;
if ((long) val < 0)
{
/* End of protection against arithmetic exceptions. */
uninstall_sigfpe_handler ();
po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false,
_("plural expression can produce negative values"));
free (array);
return 1;
}
else if (val >= nplurals_value)
{
char *msg;
/* End of protection against arithmetic exceptions. */
uninstall_sigfpe_handler ();
msg = xasprintf (_("nplurals = %lu but plural expression can produce values as large as %lu"),
nplurals_value, val);
po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
free (msg);
free (array);
return 1;
}
if (array != NULL && array[val] < OFTEN)
array[val]++;
free (array);
return 1;
}
/* End of protection against arithmetic exceptions. */
uninstall_sigfpe_handler ();
unsigned long val = res.value;
/* Normalize the array[val] statistics. */
if (array != NULL)
if ((long) val < 0)
{
unsigned long val;
for (val = 0; val < nplurals_value; val++)
array[val] = (array[val] == OFTEN ? 1 : 0);
po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false,
_("plural expression can produce negative values"));
free (array);
return 1;
}
else if (val >= nplurals_value)
{
char *msg =
xasprintf (_("nplurals = %lu but plural expression can produce values as large as %lu"),
nplurals_value, val);
po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
free (msg);
free (array);
return 1;
}
distribution->expr = plural_expr;
distribution->often = array;
distribution->often_length = (array != NULL ? nplurals_value : 0);
distribution->histogram = plural_expression_histogram;
return 0;
if (array != NULL && array[val] < OFTEN)
array[val]++;
}
else
/* Normalize the array[val] statistics. */
if (array != NULL)
{
/* Caught an arithmetic exception. */
const char *msg;
unsigned long val;
/* End of protection against arithmetic exceptions. */
uninstall_sigfpe_handler ();
#if USE_SIGINFO
switch (sigfpe_code)
#endif
{
#if USE_SIGINFO
# ifdef FPE_INTDIV
case FPE_INTDIV:
msg = _("plural expression can produce division by zero");
break;
# endif
# ifdef FPE_INTOVF
case FPE_INTOVF:
msg = _("plural expression can produce integer overflow");
break;
# endif
default:
#endif
msg = _("plural expression can produce arithmetic exceptions, possibly division by zero");
}
po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
free (array);
return 1;
for (val = 0; val < nplurals_value; val++)
array[val] = (array[val] == OFTEN ? 1 : 0);
}
distribution->expr = plural_expr;
distribution->often = array;
distribution->often_length = (array != NULL ? nplurals_value : 0);
distribution->histogram = plural_expression_histogram;
return 0;
#undef OFTEN
}

View File

@ -1,5 +1,5 @@
/* Expression evaluation for plural form selection.
Copyright (C) 2000-2003, 2005, 2019-2020 Free Software Foundation, Inc.
Copyright (C) 2000-2023 Free Software Foundation, Inc.
Written by Ulrich Drepper <drepper@cygnus.com>, 2000.
This program is free software: you can redistribute it and/or modify
@ -23,7 +23,6 @@
#include "plural-eval.h"
#include <stddef.h>
#include <signal.h>
#include "plural-exp.h"
@ -33,67 +32,3 @@
/* Include the expression evaluation code from libintl, this time with
'extern' linkage. */
#include "eval-plural.h"
/* Exit point. Must be set before calling install_sigfpe_handler(). */
sigjmp_buf sigfpe_exit;
#if USE_SIGINFO
/* Additional information that is set before sigfpe_exit is invoked. */
int volatile sigfpe_code;
/* Signal handler called in case of arithmetic exception (e.g. division
by zero) during plural_eval. */
static _GL_ASYNC_SAFE void
sigfpe_handler (int sig, siginfo_t *sip, void *scp)
{
sigfpe_code = sip->si_code;
/* This handler is invoked on the thread that caused the SIGFPE, that is,
the thread that is doing plural evaluation. Therefore it's OK to use
siglongjmp. */
siglongjmp (sigfpe_exit, 1);
}
#else
/* Signal handler called in case of arithmetic exception (e.g. division
by zero) during plural_eval. */
static _GL_ASYNC_SAFE void
sigfpe_handler (int sig)
{
/* This handler is invoked on the thread that caused the SIGFPE, that is,
the thread that is doing plural evaluation. Therefore it's OK to use
siglongjmp. */
siglongjmp (sigfpe_exit, 1);
}
#endif
void
install_sigfpe_handler (void)
{
#if USE_SIGINFO
struct sigaction action;
action.sa_sigaction = sigfpe_handler;
action.sa_flags = SA_SIGINFO;
sigemptyset (&action.sa_mask);
sigaction (SIGFPE, &action, (struct sigaction *) NULL);
#else
signal (SIGFPE, sigfpe_handler);
#endif
}
void
uninstall_sigfpe_handler (void)
{
#if USE_SIGINFO
struct sigaction action;
action.sa_handler = SIG_DFL;
action.sa_flags = 0;
sigemptyset (&action.sa_mask);
sigaction (SIGFPE, &action, (struct sigaction *) NULL);
#else
signal (SIGFPE, SIG_DFL);
#endif
}

View File

@ -24,44 +24,4 @@
#include "plural-exp.h"
/* Protection against signals during plural evaluation. */
#include <setjmp.h>
/* Some platforms don't have the sigjmp_buf type in <setjmp.h>. */
#if defined _MSC_VER || defined __MINGW32__
/* Native Woe32 API. */
# define sigjmp_buf jmp_buf
# define sigsetjmp(env,savesigs) setjmp (env)
# define siglongjmp longjmp
#endif
/* We use siginfo to get precise information about the signal.
But siginfo doesn't work on Irix 6.5 and on Cygwin 2005. */
#if HAVE_SIGINFO && !defined (__sgi) && !defined (__CYGWIN__)
# define USE_SIGINFO 1
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* Exit point. Must be set before calling install_sigfpe_handler(). */
extern sigjmp_buf sigfpe_exit;
#if USE_SIGINFO
/* Additional information that is set before sigfpe_exit is invoked. */
extern int volatile sigfpe_code;
#endif
/* Protect against signals during plural evaluation. Must be called around
calls to plural_eval(). Must be called in pairs. */
extern void install_sigfpe_handler (void);
extern void uninstall_sigfpe_handler (void);
#ifdef __cplusplus
}
#endif
#endif /* _PLURAL_EVAL_H */