#451886 fgets() and poison NULL byte attacks (aka NULL escapes)

Package:
libc6
Source:
glibc
Description:
GNU C Library: Shared libraries
Submitter:
Andrew Buckeridge
Date:
2010-06-21 05:00:03 UTC
Severity:
wishlist
#451886#5
Date:
2007-11-19 05:16:29 UTC
From:
To:
package: libc6
version: 2.3.6.ds1-13etch2
severity: wishlist

Possible partial fix for fgets and alternatives.

Bug #57729 is marked as done.  It could be fixed for real.
I have found null escapes a pretty reliable way of breaking many C
programs including various editors.

The standard C <stdio.h> fgets function is just plain wrong.

fputs stops at a '\0', but fgets may only stop at '\n' even if a '\0'
is encountered.  There is no way of knowing how much is read by fgets.
These functions are inconsistent and are not binary safe.

The standard should be fixed.

In fcat2.c I add a function int fngets(char *s, int size, FILE *stream);
which has similar functionality to GNU getline.  This can be used with
fwrite.  As fngets does not exist it could be added by any <stdio.h>
user and will be portable.

In fcat.c I simply fix the broken fputs function in such a way that it
should not break existing use.  I also suggest some macros to check the
<stdio.h> buffer so that more efficient <unistd.h> can be mixed, even
though POSIX makes it clear that this should not be done.

How many extra CPU cycles to match '\n' or '\0' rather than just
match '\n' in the library fgets functions?

I give ESMTP as an example of a new line matching problem as this often
uses <stdio.h> and is often exploited by remote.  Many other
applications match new lines and could also be fed '\0's by remote.

Trivial examples attached.  These are not thread safe.

#451886#10
Date:
2007-11-19 09:38:29 UTC
From:
To:
tag 451886 + wontfix
thanks

  The libc will follow the standard, and the standard will never ever
"fix" things like that. If you're going to read binary data, indeed, you
should not use FILE* or fgets, but plain read(1) calls with non blocking
sockets in a select loop.

  Anyway, the glibc _packaging_ team will never ever take the liberty to
introduce new symbols that arent in the upstream glibc, it would be way
too disruptive, so please bring this upstream, we don't have the
resources to do that for you.

  and yes btw fputs stops at the first \0 because it puts a string, and
strings in C are NUL terminated. fgets has a disruptive API, and _yes_ I
would very much prefer that fgets had this API intead of the current:

  ssize_t fgets(char *buf, size_t len);

  with the usual semantics that -1 means error (with errno set
properly), and else the return value would be the amount of bytes
written in the buffer buf, not counting the last '\0', meaning that the
return value would be at most equal to len - 1. Or even name this
fgetline as it's what it does for real. Sadly it's not the case, blame
the C committee. Meanwhile I'm leaving that bug as wontfix.

#451886#17
Date:
2010-06-21 05:02:28 UTC
From:
To:
In support of this not being a bug, I have code which uses the
correct, standards-compliant behavior of fgets. Contrary to Andrew's
report, it's very easy to know how much is read by fgets and use it
with data containing '\0'. Simply ensure the buffer does not contain
any occurrances of '\n' (for instance memset to all '\0') before
reading, then use memchr to search for '\n' in the results. If you
find '\n', its position tells how many bytes were read. If not, the
entire buffer was filled without reaching a newline.