diff options
authorRich Felker <>2019-02-13 18:48:04 -0500
committerRich Felker <>2019-02-13 18:48:04 -0500
commitb2020571f07beaa9873ef0e5ade456b57b589042 (patch)
parent099b89d3840c30d7dd962e18668c2e6d39f0c626 (diff)
fix behavior of gets when input line contains a null byte
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.
1 files changed, 8 insertions, 3 deletions
diff --git a/src/stdio/gets.c b/src/stdio/gets.c
index 6c4645e5..17963b93 100644
--- a/src/stdio/gets.c
+++ b/src/stdio/gets.c
@@ -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;