summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruno Haible <bruno@clisp.org>2023-03-12 11:45:11 +0100
committerBruno Haible <bruno@clisp.org>2023-03-14 02:57:28 +0100
commita03afb970c6abea8ac22b16df3cfc3419fdc795f (patch)
tree9ad765b97e5882350cc6b9115316f962e5487de4
parent589731cc1b49bfbb2467b102fba8e1afd2277eef (diff)
downloadgettext-a03afb970c6abea8ac22b16df3cfc3419fdc795f.tar.gz
xgettext: Avoid a crash due to a python-brace-format string with a NUL byte.
* gettext-tools/src/format-python-brace.c: Improve comments. (parse_directive): Don't read past the string end if c1 is NUL. * gettext-tools/tests/xgettext-python-6: New file. * gettext-tools/tests/Makefile.am (TESTS): Add it.
-rw-r--r--gettext-tools/src/format-python-brace.c52
-rw-r--r--gettext-tools/tests/Makefile.am2
-rwxr-xr-xgettext-tools/tests/xgettext-python-623
3 files changed, 64 insertions, 13 deletions
diff --git a/gettext-tools/src/format-python-brace.c b/gettext-tools/src/format-python-brace.c
index 969e58d11..229917952 100644
--- a/gettext-tools/src/format-python-brace.c
+++ b/gettext-tools/src/format-python-brace.c
@@ -32,17 +32,32 @@
#define _(str) gettext (str)
-/* Python brace format strings are defined by PEP3101 together with
- 'format' method of string class.
+/* Python brace format strings are defined by PEP3101 together with the
+ 'format' method of the string class.
A format string directive here consists of
- an opening brace '{',
- an identifier [_A-Za-z][_0-9A-Za-z]*|[0-9]+,
- - an optional getattr ('.') or getitem ('['..']') operator with
- an identifier as argument,
- - an optional format specifier starting with ':', with a
- (unnested) format string as argument,
+ - an optional sequence of
+ - getattr ('.' identifier) or
+ - getitem ('[' identifier ']')
+ operators,
+ - optionally, a ':' and a format specifier, where a format specifier is
+ - either a format directive of the form '{' ... '}' without a format
+ specifier, or
+ - of the form [[fill]align][sign][#][0][minimumwidth][.precision][type]
+ where
+ - the fill character is any character,
+ - the align flag is one of '<', '>', '=', '^',
+ - the sign is one of '+', '-', ' ',
+ - the # flag is '#',
+ - the 0 flag is '0',
+ - minimumwidth is a non-empty sequence of digits,
+ - precision is a non-empty sequence of digits,
+ - type is one of
+ - 'b', 'c', 'd', 'o', 'x', 'X', 'n' for integers,
+ - 'e', 'E', 'f', 'F', 'g', 'G', 'n', '%' for floating-point values,
- a closing brace '}'.
- Brace characters '{' and '}' can be escaped by doubles '{{' and '}}'.
+ Brace characters '{' and '}' can be escaped by doubling them: '{{' and '}}'.
*/
struct named_arg
@@ -132,7 +147,8 @@ parse_directive (struct spec *spec,
&& !parse_numeric_field (spec, &format, translated, fdi, invalid_reason))
{
*invalid_reason =
- xasprintf (_("In the directive number %u, '%c' cannot start a field name."), spec->directives, *format);
+ xasprintf (_("In the directive number %u, '%c' cannot start a field name."),
+ spec->directives, *format);
FDI_SET (format, FMTDIR_ERROR);
return false;
}
@@ -151,7 +167,8 @@ parse_directive (struct spec *spec,
invalid_reason))
{
*invalid_reason =
- xasprintf (_("In the directive number %u, '%c' cannot start a getattr argument."), spec->directives, *format);
+ xasprintf (_("In the directive number %u, '%c' cannot start a getattr argument."),
+ spec->directives, *format);
FDI_SET (format, FMTDIR_ERROR);
return false;
}
@@ -165,7 +182,8 @@ parse_directive (struct spec *spec,
invalid_reason))
{
*invalid_reason =
- xasprintf (_("In the directive number %u, '%c' cannot start a getitem argument."), spec->directives, *format);
+ xasprintf (_("In the directive number %u, '%c' cannot start a getitem argument."),
+ spec->directives, *format);
FDI_SET (format, FMTDIR_ERROR);
return false;
}
@@ -187,7 +205,8 @@ parse_directive (struct spec *spec,
if (!is_toplevel)
{
*invalid_reason =
- xasprintf (_("In the directive number %u, no more nesting is allowed in a format specifier."), spec->directives);
+ xasprintf (_("In the directive number %u, no more nesting is allowed in a format specifier."),
+ spec->directives);
FDI_SET (format, FMTDIR_ERROR);
return false;
}
@@ -197,7 +216,7 @@ parse_directive (struct spec *spec,
specifiers below, because otherwise we would need to evaluate
Python expressions by ourselves:
- - A nested format directive expanding to the whole string
+ - A nested format directive expanding to an argument
- The Standard Format Specifiers, as described in PEP3101,
not including a nested format directive */
format++;
@@ -228,6 +247,15 @@ parse_directive (struct spec *spec,
int c1, c2;
c1 = format[0];
+ if (c1 == '\0')
+ {
+ *invalid_reason =
+ xasprintf (_("In the directive number %u, there is an unterminated format directive."),
+ spec->directives);
+ FDI_SET (format, FMTDIR_ERROR);
+ return false;
+ }
+
c2 = format[1];
if (c2 == '<' || c2 == '>' || c2 == '=' || c2 == '^')
diff --git a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am
index b0d96c83e..cfbc7179c 100644
--- a/gettext-tools/tests/Makefile.am
+++ b/gettext-tools/tests/Makefile.am
@@ -139,7 +139,7 @@ TESTS = gettext-1 gettext-2 \
xgettext-properties-4 \
xgettext-rst-1 xgettext-rst-2 \
xgettext-python-1 xgettext-python-2 xgettext-python-3 \
- xgettext-python-4 xgettext-python-5 \
+ xgettext-python-4 xgettext-python-5 xgettext-python-6 \
xgettext-python-stackovfl-1 xgettext-python-stackovfl-2 \
xgettext-python-stackovfl-3 xgettext-python-stackovfl-4 \
xgettext-ruby-1 \
diff --git a/gettext-tools/tests/xgettext-python-6 b/gettext-tools/tests/xgettext-python-6
new file mode 100755
index 000000000..72657a0bb
--- /dev/null
+++ b/gettext-tools/tests/xgettext-python-6
@@ -0,0 +1,23 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Python support: a python-brace-format string with a NUL byte.
+
+tr X '\0' <<\EOF > xg-py-6.py
+_("{0:X>}")
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-py-6.tmp xg-py-6.py || Exit 1
+LC_ALL=C tr -d '\r' < xg-py-6.tmp.po > xg-py-6.po || Exit 1
+
+cat <<EOF > xg-py-6.ok
+msgid "{0:"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-py-6.ok xg-py-6.po
+result=$?
+
+exit $result