fix behavior of gets when input line contains a null byte
authorRich Felker <dalias@aerifal.cx>
Wed, 13 Feb 2019 23:48:04 +0000 (18:48 -0500)
committerRich Felker <dalias@aerifal.cx>
Wed, 13 Feb 2019 23:48:04 +0000 (18:48 -0500)
the way gets was implemented in terms of fgets, it used the location
of the null termination to determine where to find and remove the
newline, if any. an embedded null byte prevented this from working.

this also fixes a one-byte buffer overflow, whereby when gets read an
N-byte line (not counting newline), it would store two null
terminators for a total of N+2 bytes. it's unlikely that anyone would
care that a function whose use is pretty much inherently a buffer
overflow writes too much, but it could break the only possible correct
uses of this function, in conjunction with input of known format from
a trusted/same-privilege-domain source, where the buffer length may
have been selected to exactly match a line length contract.

there seems to be no correct way to implement gets in terms of a
single call to fgets or scanf, and using multiple calls would require
explicit locking, so we might as well just write the logic out
explicitly character-at-a-time. this isn't fast, but nobody cares if a
catastrophically unsafe function that's so bad it was removed from the
C language is fast.

src/stdio/gets.c

index 6c4645e5640b7b9d4df8387346a02122dfc0f5ea..17963b93e39223a031dfbd661e34160e5fe81b58 100644 (file)
@@ -4,7 +4,12 @@
 
 char *gets(char *s)
 {
-       char *ret = fgets(s, INT_MAX, stdin);
-       if (ret && s[strlen(s)-1] == '\n') s[strlen(s)-1] = 0;
-       return ret;
+       size_t i=0;
+       int c;
+       FLOCK(stdin);
+       while ((c=getc_unlocked(stdin)) != EOF && c != '\n') s[i++] = c;
+       s[i] = 0;
+       if (c != '\n' && (!feof(stdin) || !i)) s = 0;
+       FUNLOCK(stdin);
+       return s;
 }