[v2,1/2] intl: Import plural expression hardening from GNU gettext

Message ID 20260501112421.3373616-2-avinal.xlvii@gmail.com (mailing list archive)
State Superseded
Headers
Series intl: Import plural expression hardening from GNU gettext |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent

Commit Message

Avinal Kumar May 1, 2026, 11:24 a.m. UTC
  The plural expression evaluator plural_eval() in eval-plural.h uses
unbounded recursion, which can cause a stack overflow crash with
deeply nested expressions in malicious .mo files.  This is
particularly dangerous on threads with small stacks (musl libc
default: 128 KB, AIX 7 default: 96 KB, glibc after ulimit -s 260:
~3919 recursions max).

Additionally, division by zero in plural expressions triggers
raise(SIGFPE), which is not multithread-safe, catching SIGFPE
requires per-process signal handlers that race with other threads.

Fix both by importing the hardening from GNU gettext:

- Replace unbounded plural_eval() with depth-limited
  plural_eval_recurse() (EVAL_MAXDEPTH=100), returning a
  struct eval_result with status instead of a bare unsigned long.

- Return PE_INTDIV status on division by zero instead of raising
  SIGFPE.  Remove the architecture-specific INTDIV0_RAISES_SIGFPE
  macro and the conditional #include <signal.h>.

- Update plural_lookup() in dcigettext.c to handle the new return
  type, falling back to index 0 on any evaluation failure.

Based on GNU gettext commits ef37a1540 and 726bfb1d1.
Original author: Bruno Haible <bruno@clisp.org>
Discussed on: https://sourceware.org/pipermail/libc-alpha/2023-October/152010.html

Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
---
 intl/dcigettext.c  |  27 +++------
 intl/eval-plural.h | 139 ++++++++++++++++++++++++++++++++++-----------
 intl/plural-exp.h  |  21 ++++++-
 3 files changed, 134 insertions(+), 53 deletions(-)
  

Comments

Adhemerval Zanella Netto May 4, 2026, 1:02 p.m. UTC | #1
On 01/05/26 08:24, Avinal Kumar wrote:
> The plural expression evaluator plural_eval() in eval-plural.h uses
> unbounded recursion, which can cause a stack overflow crash with
> deeply nested expressions in malicious .mo files.  This is
> particularly dangerous on threads with small stacks (musl libc
> default: 128 KB, AIX 7 default: 96 KB, glibc after ulimit -s 260:
> ~3919 recursions max).
> 
> Additionally, division by zero in plural expressions triggers
> raise(SIGFPE), which is not multithread-safe, catching SIGFPE
> requires per-process signal handlers that race with other threads.
> 
> Fix both by importing the hardening from GNU gettext:
> 
> - Replace unbounded plural_eval() with depth-limited
>   plural_eval_recurse() (EVAL_MAXDEPTH=100), returning a
>   struct eval_result with status instead of a bare unsigned long.
> 
> - Return PE_INTDIV status on division by zero instead of raising
>   SIGFPE.  Remove the architecture-specific INTDIV0_RAISES_SIGFPE
>   macro and the conditional #include <signal.h>.
> 
> - Update plural_lookup() in dcigettext.c to handle the new return
>   type, falling back to index 0 on any evaluation failure.
> 
> Based on GNU gettext commits ef37a1540 and 726bfb1d1.
> Original author: Bruno Haible <bruno@clisp.org>
> Discussed on: https://sourceware.org/pipermail/libc-alpha/2023-October/152010.html
> 
> Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>

LGTM, thanks.

Reviwed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  intl/dcigettext.c  |  27 +++------
>  intl/eval-plural.h | 139 ++++++++++++++++++++++++++++++++++-----------
>  intl/plural-exp.h  |  21 ++++++-
>  3 files changed, 134 insertions(+), 53 deletions(-)
> 
> diff --git a/intl/dcigettext.c b/intl/dcigettext.c
> index 9ebc54eeec..fb948c1092 100644
> --- a/intl/dcigettext.c
> +++ b/intl/dcigettext.c
> @@ -68,20 +68,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 __s390x__
> -#  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
> @@ -1381,14 +1367,19 @@ plural_lookup (struct loaded_l10nfile *domain, unsigned long int n,
>  	       const char *translation, size_t translation_len)
>  {
>    struct loaded_domain *domaindata = (struct loaded_domain *) domain->data;
> +  struct eval_result result;
>    unsigned long int index;
>    const char *p;
>  
> -  index = plural_eval (domaindata->plural, n);
> -  if (index >= domaindata->nplurals)
> -    /* This should never happen.  It means the plural expression and the
> -       given maximum value do not match.  */
> +  result = plural_eval (domaindata->plural, n);
> +  if (result.status != PE_OK)
> +    /* The plural expression evaluation failed.  */
>      index = 0;
> +  else if (result.value >= domaindata->nplurals)
> +    /* The plural expression and the given maximum value do not match.  */
> +    index = 0;
> +  else
> +    index = result.value;
>  
>    /* Skip INDEX strings at TRANSLATION.  */
>    p = translation;
> diff --git a/intl/eval-plural.h b/intl/eval-plural.h
> index 243f6027a1..e231e9bfd3 100644
> --- a/intl/eval-plural.h
> +++ b/intl/eval-plural.h
> @@ -14,24 +14,59 @@
>     You should have received a copy of the GNU Lesser General Public License
>     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
>  
> +/* Written by Ulrich Drepper and Bruno Haible.  */
> +
>  #ifndef STATIC
>  #define STATIC static
>  #endif
>  
> -/* Evaluate the plural expression and return an index value.  */
> -STATIC
> -unsigned long int
> -plural_eval (const struct expression *pexp, unsigned long int n)
> +/* While the bison parser is able to support expressions of a maximum depth
> +   of YYMAXDEPTH = 10000, the runtime evaluation of a parsed plural expression
> +   has a smaller maximum recursion depth.
> +   If we did not limit the recursion depth, a program that just invokes
> +   ngettext() on a thread other than the main thread could get a crash by
> +   stack overflow in the following circumstances:
> +     - On systems with glibc, after the stack size has been reduced,
> +       e.g. on x86_64 systems after "ulimit -s 260".
> +       This stack size is only sufficient for ca. 3919 recursions.
> +       Cf. <https://unix.stackexchange.com/questions/620720/>
> +     - On systems with musl libc, because there the thread stack size (for a
> +       thread other than the main thread) by default is only 128 KB, see
> +       <https://wiki.musl-libc.org/functional-differences-from-glibc.html#Thread-stack-size>.
> +     - On AIX 7 systems, because there the thread stack size (for a thread
> +       other than the main thread) by default is only 96 KB, see
> +       <https://www.ibm.com/docs/en/aix/7.1?topic=programming-threads-library-options>.
> +       This stack size is only sufficient for between 887 and 1363 recursions,
> +       depending on the compiler and compiler optimization options.
> +   A maximum depth of 100 is a large enough for all practical needs
> +   and also small enough to avoid stack overflow even with small thread stack
> +   sizes.  */
> +#ifndef EVAL_MAXDEPTH
> +# define EVAL_MAXDEPTH 100
> +#endif
> +
> +/* A shorthand that denotes a successful evaluation result with a value V.  */
> +#define OK(v) (struct eval_result) { .status = PE_OK, .value = (v) }
> +
> +/* Evaluates a plural expression PEXP for n=N, up to ALLOWED_DEPTH.  */
> +static struct eval_result
> +plural_eval_recurse (const struct expression *pexp, unsigned long int n,
> +		     unsigned int allowed_depth)
>  {
> +  if (allowed_depth == 0)
> +    /* The allowed recursion depth is exhausted.  */
> +    return (struct eval_result) { .status = PE_STACKOVF };
> +  allowed_depth--;
> +
>    switch (pexp->nargs)
>      {
>      case 0:
>        switch (pexp->operation)
>  	{
>  	case var:
> -	  return n;
> +	  return OK (n);
>  	case num:
> -	  return pexp->val.num;
> +	  return OK (pexp->val.num);
>  	default:
>  	  break;
>  	}
> @@ -40,52 +75,73 @@ plural_eval (const struct expression *pexp, unsigned long int n)
>      case 1:
>        {
>  	/* pexp->operation must be lnot.  */
> -	unsigned long int arg = plural_eval (pexp->val.args[0], n);
> -	return ! arg;
> +	struct eval_result arg =
> +	  plural_eval_recurse (pexp->val.args[0], n, allowed_depth);
> +	if (arg.status != PE_OK)
> +	  return arg;
> +	return OK (! arg.value);
>        }
>      case 2:
>        {
> -	unsigned long int leftarg = plural_eval (pexp->val.args[0], n);
> +	struct eval_result leftarg =
> +	  plural_eval_recurse (pexp->val.args[0], n, allowed_depth);
> +	if (leftarg.status != PE_OK)
> +	  return leftarg;
>  	if (pexp->operation == lor)
> -	  return leftarg || plural_eval (pexp->val.args[1], n);
> +	  {
> +	    if (leftarg.value)
> +	      return OK (1);
> +	    struct eval_result rightarg =
> +	      plural_eval_recurse (pexp->val.args[1], n, allowed_depth);
> +	    if (rightarg.status != PE_OK)
> +	      return rightarg;
> +	    return OK (rightarg.value ? 1 : 0);
> +	  }
>  	else if (pexp->operation == land)
> -	  return leftarg && plural_eval (pexp->val.args[1], n);
> +	  {
> +	    if (!leftarg.value)
> +	      return OK (0);
> +	    struct eval_result rightarg =
> +	      plural_eval_recurse (pexp->val.args[1], n, allowed_depth);
> +	    if (rightarg.status != PE_OK)
> +	      return rightarg;
> +	    return OK (rightarg.value ? 1 : 0);
> +	  }
>  	else
>  	  {
> -	    unsigned long int rightarg = plural_eval (pexp->val.args[1], n);
> +	    struct eval_result rightarg =
> +	      plural_eval_recurse (pexp->val.args[1], n, allowed_depth);
> +	    if (rightarg.status != PE_OK)
> +	      return rightarg;
>  
>  	    switch (pexp->operation)
>  	      {
>  	      case mult:
> -		return leftarg * rightarg;
> +		return OK (leftarg.value * rightarg.value);
>  	      case divide:
> -#if !INTDIV0_RAISES_SIGFPE
> -		if (rightarg == 0)
> -		  raise (SIGFPE);
> -#endif
> -		return leftarg / rightarg;
> +		if (rightarg.value == 0)
> +		  return (struct eval_result) { .status = PE_INTDIV };
> +		return OK (leftarg.value / rightarg.value);
>  	      case module:
> -#if !INTDIV0_RAISES_SIGFPE
> -		if (rightarg == 0)
> -		  raise (SIGFPE);
> -#endif
> -		return leftarg % rightarg;
> +		if (rightarg.value == 0)
> +		  return (struct eval_result) { .status = PE_INTDIV };
> +		return OK (leftarg.value % rightarg.value);
>  	      case plus:
> -		return leftarg + rightarg;
> +		return OK (leftarg.value + rightarg.value);
>  	      case minus:
> -		return leftarg - rightarg;
> +		return OK (leftarg.value - rightarg.value);
>  	      case less_than:
> -		return leftarg < rightarg;
> +		return OK (leftarg.value < rightarg.value);
>  	      case greater_than:
> -		return leftarg > rightarg;
> +		return OK (leftarg.value > rightarg.value);
>  	      case less_or_equal:
> -		return leftarg <= rightarg;
> +		return OK (leftarg.value <= rightarg.value);
>  	      case greater_or_equal:
> -		return leftarg >= rightarg;
> +		return OK (leftarg.value >= rightarg.value);
>  	      case equal:
> -		return leftarg == rightarg;
> +		return OK (leftarg.value == rightarg.value);
>  	      case not_equal:
> -		return leftarg != rightarg;
> +		return OK (leftarg.value != rightarg.value);
>  	      default:
>  		break;
>  	      }
> @@ -96,10 +152,25 @@ plural_eval (const struct expression *pexp, unsigned long int n)
>      case 3:
>        {
>  	/* pexp->operation must be qmop.  */
> -	unsigned long int boolarg = plural_eval (pexp->val.args[0], n);
> -	return plural_eval (pexp->val.args[boolarg ? 1 : 2], n);
> +	struct eval_result boolarg =
> +	  plural_eval_recurse (pexp->val.args[0], n, allowed_depth);
> +	if (boolarg.status != PE_OK)
> +	  return boolarg;
> +	return plural_eval_recurse (pexp->val.args[boolarg.value ? 1 : 2], n,
> +				    allowed_depth);
>        }
>      }
>    /* NOTREACHED */
> -  return 0;
> +  return (struct eval_result) { .status = PE_ASSERT };
> +}
> +
> +/* Evaluates a plural expression PEXP for n=N.  */
> +STATIC
> +struct eval_result
> +plural_eval (const struct expression *pexp, unsigned long int n)
> +{
> +  return plural_eval_recurse (pexp, n, EVAL_MAXDEPTH);
>  }
> +
> +#undef OK
> +
> diff --git a/intl/plural-exp.h b/intl/plural-exp.h
> index f0d3a611e1..e2e0c30563 100644
> --- a/intl/plural-exp.h
> +++ b/intl/plural-exp.h
> @@ -26,6 +26,7 @@
>  extern "C" {
>  #endif
>  
> +/* Parsing a plural expression.  */
>  
>  enum expression_operator
>  {
> @@ -109,8 +110,26 @@ extern void EXTRACT_PLURAL_EXPRESSION (const char *nullentry,
>  				       unsigned long int *npluralsp)
>       attribute_hidden;
>  
> +/* Evaluating a parsed plural expression.  */
> +
> +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 */
> +};
> +
> +struct eval_result
> +{
> +  enum eval_status status;
> +  unsigned long int value;      /* Only relevant for status == PE_OK */
> +};
> +
> +
>  #if !defined (_LIBC) && !defined (IN_LIBINTL) && !defined (IN_LIBGLOCALE)
> -extern unsigned long int plural_eval (const struct expression *pexp,
> +extern struct eval_result plural_eval (const struct expression *pexp,
>  				      unsigned long int n);
>  #endif
>
  

Patch

diff --git a/intl/dcigettext.c b/intl/dcigettext.c
index 9ebc54eeec..fb948c1092 100644
--- a/intl/dcigettext.c
+++ b/intl/dcigettext.c
@@ -68,20 +68,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 __s390x__
-#  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
@@ -1381,14 +1367,19 @@  plural_lookup (struct loaded_l10nfile *domain, unsigned long int n,
 	       const char *translation, size_t translation_len)
 {
   struct loaded_domain *domaindata = (struct loaded_domain *) domain->data;
+  struct eval_result result;
   unsigned long int index;
   const char *p;
 
-  index = plural_eval (domaindata->plural, n);
-  if (index >= domaindata->nplurals)
-    /* This should never happen.  It means the plural expression and the
-       given maximum value do not match.  */
+  result = plural_eval (domaindata->plural, n);
+  if (result.status != PE_OK)
+    /* The plural expression evaluation failed.  */
     index = 0;
+  else if (result.value >= domaindata->nplurals)
+    /* The plural expression and the given maximum value do not match.  */
+    index = 0;
+  else
+    index = result.value;
 
   /* Skip INDEX strings at TRANSLATION.  */
   p = translation;
diff --git a/intl/eval-plural.h b/intl/eval-plural.h
index 243f6027a1..e231e9bfd3 100644
--- a/intl/eval-plural.h
+++ b/intl/eval-plural.h
@@ -14,24 +14,59 @@ 
    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
 
+/* Written by Ulrich Drepper and Bruno Haible.  */
+
 #ifndef STATIC
 #define STATIC static
 #endif
 
-/* Evaluate the plural expression and return an index value.  */
-STATIC
-unsigned long int
-plural_eval (const struct expression *pexp, unsigned long int n)
+/* While the bison parser is able to support expressions of a maximum depth
+   of YYMAXDEPTH = 10000, the runtime evaluation of a parsed plural expression
+   has a smaller maximum recursion depth.
+   If we did not limit the recursion depth, a program that just invokes
+   ngettext() on a thread other than the main thread could get a crash by
+   stack overflow in the following circumstances:
+     - On systems with glibc, after the stack size has been reduced,
+       e.g. on x86_64 systems after "ulimit -s 260".
+       This stack size is only sufficient for ca. 3919 recursions.
+       Cf. <https://unix.stackexchange.com/questions/620720/>
+     - On systems with musl libc, because there the thread stack size (for a
+       thread other than the main thread) by default is only 128 KB, see
+       <https://wiki.musl-libc.org/functional-differences-from-glibc.html#Thread-stack-size>.
+     - On AIX 7 systems, because there the thread stack size (for a thread
+       other than the main thread) by default is only 96 KB, see
+       <https://www.ibm.com/docs/en/aix/7.1?topic=programming-threads-library-options>.
+       This stack size is only sufficient for between 887 and 1363 recursions,
+       depending on the compiler and compiler optimization options.
+   A maximum depth of 100 is a large enough for all practical needs
+   and also small enough to avoid stack overflow even with small thread stack
+   sizes.  */
+#ifndef EVAL_MAXDEPTH
+# define EVAL_MAXDEPTH 100
+#endif
+
+/* A shorthand that denotes a successful evaluation result with a value V.  */
+#define OK(v) (struct eval_result) { .status = PE_OK, .value = (v) }
+
+/* Evaluates a plural expression PEXP for n=N, up to ALLOWED_DEPTH.  */
+static struct eval_result
+plural_eval_recurse (const struct expression *pexp, unsigned long int n,
+		     unsigned int allowed_depth)
 {
+  if (allowed_depth == 0)
+    /* The allowed recursion depth is exhausted.  */
+    return (struct eval_result) { .status = PE_STACKOVF };
+  allowed_depth--;
+
   switch (pexp->nargs)
     {
     case 0:
       switch (pexp->operation)
 	{
 	case var:
-	  return n;
+	  return OK (n);
 	case num:
-	  return pexp->val.num;
+	  return OK (pexp->val.num);
 	default:
 	  break;
 	}
@@ -40,52 +75,73 @@  plural_eval (const struct expression *pexp, unsigned long int n)
     case 1:
       {
 	/* pexp->operation must be lnot.  */
-	unsigned long int arg = plural_eval (pexp->val.args[0], n);
-	return ! arg;
+	struct eval_result arg =
+	  plural_eval_recurse (pexp->val.args[0], n, allowed_depth);
+	if (arg.status != PE_OK)
+	  return arg;
+	return OK (! arg.value);
       }
     case 2:
       {
-	unsigned long int leftarg = plural_eval (pexp->val.args[0], n);
+	struct eval_result leftarg =
+	  plural_eval_recurse (pexp->val.args[0], n, allowed_depth);
+	if (leftarg.status != PE_OK)
+	  return leftarg;
 	if (pexp->operation == lor)
-	  return leftarg || plural_eval (pexp->val.args[1], n);
+	  {
+	    if (leftarg.value)
+	      return OK (1);
+	    struct eval_result rightarg =
+	      plural_eval_recurse (pexp->val.args[1], n, allowed_depth);
+	    if (rightarg.status != PE_OK)
+	      return rightarg;
+	    return OK (rightarg.value ? 1 : 0);
+	  }
 	else if (pexp->operation == land)
-	  return leftarg && plural_eval (pexp->val.args[1], n);
+	  {
+	    if (!leftarg.value)
+	      return OK (0);
+	    struct eval_result rightarg =
+	      plural_eval_recurse (pexp->val.args[1], n, allowed_depth);
+	    if (rightarg.status != PE_OK)
+	      return rightarg;
+	    return OK (rightarg.value ? 1 : 0);
+	  }
 	else
 	  {
-	    unsigned long int rightarg = plural_eval (pexp->val.args[1], n);
+	    struct eval_result rightarg =
+	      plural_eval_recurse (pexp->val.args[1], n, allowed_depth);
+	    if (rightarg.status != PE_OK)
+	      return rightarg;
 
 	    switch (pexp->operation)
 	      {
 	      case mult:
-		return leftarg * rightarg;
+		return OK (leftarg.value * rightarg.value);
 	      case divide:
-#if !INTDIV0_RAISES_SIGFPE
-		if (rightarg == 0)
-		  raise (SIGFPE);
-#endif
-		return leftarg / rightarg;
+		if (rightarg.value == 0)
+		  return (struct eval_result) { .status = PE_INTDIV };
+		return OK (leftarg.value / rightarg.value);
 	      case module:
-#if !INTDIV0_RAISES_SIGFPE
-		if (rightarg == 0)
-		  raise (SIGFPE);
-#endif
-		return leftarg % rightarg;
+		if (rightarg.value == 0)
+		  return (struct eval_result) { .status = PE_INTDIV };
+		return OK (leftarg.value % rightarg.value);
 	      case plus:
-		return leftarg + rightarg;
+		return OK (leftarg.value + rightarg.value);
 	      case minus:
-		return leftarg - rightarg;
+		return OK (leftarg.value - rightarg.value);
 	      case less_than:
-		return leftarg < rightarg;
+		return OK (leftarg.value < rightarg.value);
 	      case greater_than:
-		return leftarg > rightarg;
+		return OK (leftarg.value > rightarg.value);
 	      case less_or_equal:
-		return leftarg <= rightarg;
+		return OK (leftarg.value <= rightarg.value);
 	      case greater_or_equal:
-		return leftarg >= rightarg;
+		return OK (leftarg.value >= rightarg.value);
 	      case equal:
-		return leftarg == rightarg;
+		return OK (leftarg.value == rightarg.value);
 	      case not_equal:
-		return leftarg != rightarg;
+		return OK (leftarg.value != rightarg.value);
 	      default:
 		break;
 	      }
@@ -96,10 +152,25 @@  plural_eval (const struct expression *pexp, unsigned long int n)
     case 3:
       {
 	/* pexp->operation must be qmop.  */
-	unsigned long int boolarg = plural_eval (pexp->val.args[0], n);
-	return plural_eval (pexp->val.args[boolarg ? 1 : 2], n);
+	struct eval_result boolarg =
+	  plural_eval_recurse (pexp->val.args[0], n, allowed_depth);
+	if (boolarg.status != PE_OK)
+	  return boolarg;
+	return plural_eval_recurse (pexp->val.args[boolarg.value ? 1 : 2], n,
+				    allowed_depth);
       }
     }
   /* NOTREACHED */
-  return 0;
+  return (struct eval_result) { .status = PE_ASSERT };
+}
+
+/* Evaluates a plural expression PEXP for n=N.  */
+STATIC
+struct eval_result
+plural_eval (const struct expression *pexp, unsigned long int n)
+{
+  return plural_eval_recurse (pexp, n, EVAL_MAXDEPTH);
 }
+
+#undef OK
+
diff --git a/intl/plural-exp.h b/intl/plural-exp.h
index f0d3a611e1..e2e0c30563 100644
--- a/intl/plural-exp.h
+++ b/intl/plural-exp.h
@@ -26,6 +26,7 @@ 
 extern "C" {
 #endif
 
+/* Parsing a plural expression.  */
 
 enum expression_operator
 {
@@ -109,8 +110,26 @@  extern void EXTRACT_PLURAL_EXPRESSION (const char *nullentry,
 				       unsigned long int *npluralsp)
      attribute_hidden;
 
+/* Evaluating a parsed plural expression.  */
+
+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 */
+};
+
+struct eval_result
+{
+  enum eval_status status;
+  unsigned long int value;      /* Only relevant for status == PE_OK */
+};
+
+
 #if !defined (_LIBC) && !defined (IN_LIBINTL) && !defined (IN_LIBGLOCALE)
-extern unsigned long int plural_eval (const struct expression *pexp,
+extern struct eval_result plural_eval (const struct expression *pexp,
 				      unsigned long int n);
 #endif