Hi Bertrand,

Bertrand Garrigues via wrote on Thu, Oct 15, 2020 at 12:03:44AM +0200:
> On Mon, Oct 12 2020 at 06:08:27 PM, Ingo Schwarze <schwa...@usta.de> wrote:

>> A major problem with gnulib emerged just now that we should perhaps
>> treat as a blocker.
> [...]
>> The code in
>>
>>   gnulib/lib/vasnprintf.c
>>
>> line 4879 puts a format string containing a %n directive into
>> writeable memory and subsequently passes that memory as a first
>> argument to printf(3).
>>
>> Using %n at all is insecure programming practice.
> [...]

> That seems to be only for old versions of glibc or a few operating
> systems:
> 
>    # if ! (((__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)) \
>             && !defined __UCLIBC__)                                     \
>            || (defined __APPLE__ && defined __MACH__)                   \
>            || (defined _WIN32 && ! defined __CYGWIN__))
>                    fbp[1] = '%';
>                    fbp[2] = 'n';
>                    fbp[3] = '\0';
>    # else

Well, what that code means is "*everywhere* except on newer glibc
and some cases of Apple and Windows" - so it did trigger on OpenBSD.

> From what I understand in their comment after the #else they try do to
> their best to avoid using %n.  You are referring to line 4879, which
> means you were looking on the gnulib integrated on the 1.22.4.  We also
> have the same logic on the up-to-date gnulib (line 5126):
> 
>    /* On systems where we know that snprintf's return value
>       conforms to ISO C 99 (HAVE_SNPRINTF_RETVAL_C99) and that
>       snprintf always produces NUL-terminated strings
>       (HAVE_SNPRINTF_TRUNCATION_C99), it is possible to avoid
>       using %n
>    [...]

Oh, you have a point here.  After your recent gnulib update - thanks
for doing that, even though it caused a minor glitch, see my Savannah
ticket 59276, keeping libraries up to date does make sense - the
condition is now inverted and reads as follows:

  # if ((HAVE_SNPRINTF_RETVAL_C99 && HAVE_SNPRINTF_TRUNCATION_C99)    \
      || ((__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)) \
          && !defined __UCLIBC__)                                     \
      || (defined __APPLE__ && defined __MACH__)                      \
      || defined __ANDROID__                                          \
      || (defined _WIN32 && ! defined __CYGWIN__))

Even though the nasty OS-tests are still there, they are now obsolete
and instead, %n is only used if HAVE_SNPRINTF_RETVAL_C99
or HAVE_SNPRINTF_TRUNCATION_C99 (or both) fail.

Both succeed on OpenBSD, so i confirmed with the follwoing command
that the insecure code with %n is no longer compiled, for now:

  gcc -std=gnu99 -DHAVE_CONFIG_H -I. -I.. -I./src/include
  -I../src/include -I../lib -I./src/include -I./lib -I/usr/local/include
  -g -O2 -c -save-temps -o lib/vasnprintf.o ../lib/vasnprintf.c

(Notice the -save-temps in there.)

Meanwhile, i heard rumours that the upcoming change in libc that will
cause %n in writeable memory to abort the program will cause some
gnulib ./configure tests to (wrongly) fail, again resulting in
compilation of vasnprintf.c with %n and ultimately killing groff
at run-time.  I'll have to check whether such a problem really
exists for groff.  If yes, i'll have to investigate further and
find a fix.  If not, all this can be postponed until after release.

>    #else      /* AIX <= 5.1, HP-UX, IRIX, OSF/1, Solaris <= 9, BeOS */

Uh oh.  Are we really trying to support *those*?
Well, maybe that's a different discussion...

>                    fbp[1] = '%';
>                    fbp[2] = 'n';
>                    fbp[3] = '\0';
>    #endif
> 
> Does your build bump into the code after the #else?

It did until last week, it does not right now, but i have to check
whether it will start doing that again in the near future.
I'll come back to you after checking that.

>> The way gnulib detects whether to compile that code seems to be
>> very broken.  On the one hand, gnulib performs extremely complex
>> tests that resemble an (overly aggressive) regression suite rather
>> than mere feature tests.  If i understand correctly, if *anything*
>> in that suite fails, no matter whether it's important or just a
>> very weird corner case, gnulib tends to compile the file
>> gnulib/lib/vasnprintf.c.

> I think we have vasnprintf.c only because it is a dependency of the
> vsnprintf module (listed in 'gnulib_modules' in bootstrap.conf).

Are you really sure?

Merely for testing (not intended for commit at this point),
i just deleted "vsnprintf" from bootstrap.conf.  While that does
remove "vsnprintf" , "lib/vsnprintf.c", and "m4/vsnprintf.m4"
from bootstrap output, "vasnprintf" is still there as a dependency
of "snprintf", and "lib/vasnprintf.c" and "m4/vasnprintf.m4" are
still in the file list.  Also, vasnprintf.c still gets compiled
for me, presumably because (hard to be sure due to the complexity,
but that's my guess at this point):

  checking whether printf supports the 'a' and 'A' directives... no
  checking whether printf survives out-of-memory conditions... no

Of course, these two results are bogus.  We do support %a/%A, and
i didn't investigate yet what the second one means, but whatever
it may mean, that's clearly not a good reason to replace native
printf(3).

>> On the other hand, that file does lots
>> of very complicated version number and operating system name tests
>> on its own at compile time rather than at ./configure time, so it
>> is very difficult to figure out what is actually happening, and
>> even more so to check whether what is happening in a specific
>> compilation run is correct.
>>
>> For example, it appears that one among large numbers of effects
>> that can trigger compilation of gnulib/lib/vasnprintf.c is that the
>> operating system denies %n in writeable memory, and as a consequence
>> gnulib/lib/vasnprintf.c is compiled and then proceeds to use %n in
>> writeable memory.
> [...]

> From what I understand we build this code only on some old platforms.
> The code that uses %n is not built on my system.

You probably have glibc, right?

> [...]
>> While i did rarely run into operating systems that failed to provide
>> the non-standard function vasprintf(3) - a GNU extension also
>> available in all BSDs - groff does not use that function.  I do not
>> remember ever running into a system lacking any of the standard
>> functions * fprintf(3) - first appeared in Version 7 AT&T UNIX *
>> snprintf(3) and vsnprintf(3) - first in 4.4BSD
>>
>> So my favourite solution would be to just stop using all the gnulib
>> *printf* modules.  I'm not aware of any portability problems they
>> might help to solve, not even on historic systems like Solaris 9,

> the *printf* modules from gnulib were used in replacement of some
> printf functions implemented directly in groff's source code.

That's probably not a good idea either.

> I believe that
> the gnulib's versions are much better maintained.  Perhaps that now
> groff builds fine without any replacement function at all, but that
> would require to spend extra time to check that, so unless it is clearly
> proven that there is a problem in gnulib's replacement functions I don't
> see any reason to remove them.

If i manage to confirm that the tests for HAVE_SNPRINTF_RETVAL_C99
and HAVE_SNPRINTF_TRUNCATION_C99 are reasoably robust, we can
postpone further deliberation until after release, i think.

>> but they most definitly cause severe portability and security issues
>> on several operating systems, in particular on modern ones.
>>
>> What do you think?
>>
>> Should i do testing without these three modules on OpenBSD, Linux,
>> and various versions of Solaris to see if that improves matters?

> I think that unless you find out that the code using %n is really
> compiled and used on one of your build, it's not worth spending time on
> that.

I will re-test ASAP with the upcoming libc change; to save time,
i will do so without waiting for that change to be committed.

Yours,
  Ingo

Reply via email to