gets() is dead

C

Charles Richmond

Harald said:
Flash said:
Harald van Dijk wrote, On 29/04/07 20:57:

>> [snip...] [snip...] [snip...]
>>
An implementation not providing gets is just as broken as far as the
standard is concerned as an implementation providing a broken one.

Right, but as I tried to explain, this could actually be useful.
Programs using gets() would not run at all, rather than run with
incorrect behaviour, and it's the running with incorrect behaviour
that I mainly object to.

Perhaps I was *not* clear. The "gets()" function does *not*
print a warning each time it is called. Instead, *one* warning
is printed when the program is started. Somehow, using "gets()"
causes this one-time behavior to be enabled or loaded from
the "libc" library.

I can fully understand why you folks do *not* want to use
"gets()". Still, for my purposes, it works well. Why do you
deprecate my use of it so much??? If I had to write something
that needed to be secure, I would certainly *not* use "gets()".
 
D

Dave Vandervies

Charles Richmond said:
I can fully understand why you folks do *not* want to use
"gets()". Still, for my purposes, it works well. Why do you
deprecate my use of it so much??? If I had to write something
that needed to be secure, I would certainly *not* use "gets()".

There's no such thing as code that doesn't need to be secure.
And if there was, you would find it transmogrifying into code that *does*
need to be secure at incredibly inconvenient times.


dave
 
K

Keith Thompson

Charles Richmond said:
Harald said:
Flash said:
Harald van Dijk wrote, On 29/04/07 20:57:

[snip...] [snip...] [snip...]

An implementation not providing gets is just as broken as far as the
standard is concerned as an implementation providing a broken one.
Right, but as I tried to explain, this could actually be useful.
Programs using gets() would not run at all, rather than run with
incorrect behaviour, and it's the running with incorrect behaviour
that I mainly object to.

Perhaps I was *not* clear. The "gets()" function does *not*
print a warning each time it is called. Instead, *one* warning
is printed when the program is started. Somehow, using "gets()"
causes this one-time behavior to be enabled or loaded from
the "libc" library.

Ok. I don't know of any C library implementation that behaves this
way, but I'll take your word for it.
I can fully understand why you folks do *not* want to use
"gets()". Still, for my purposes, it works well. Why do you
deprecate my use of it so much??? If I had to write something
that needed to be secure, I would certainly *not* use "gets()".

If you insist on writing unsafe code, nobody can stop you. It's
trivial to write your own function that works the same way gets()
does. (It's also trivial to write an equivalent of gets() that
doesn't have the same problem, something like fgets() that deletes the
trailing '\n' character.)

But if you want to shoot yourself in the foot, don't expect us to load
the gun for you. We just *might* unknowingly run your code ourselves
some day. And if your problem with C is that it does too much to
prevent buffer overflows, I'd rather not do that.
 
R

Richard Tobin

Harald van Dijk said:
If a build-time error is issued, code using gets will need to be
changed. A conforming implementation cannot do this, but for this I
might agree that it's broken in a somewhat useful way, though I would
still not use it.

I think a lot of people wouldn't use it, since fixing such things is
not everyone's highest priority. Often it's more important to get
something running than to protect against errors that have a low
probability of occurring.
If a run-time warning is issued, code using gets
will continue to build, but run incorrectly. A conforming
implementation cannot do this, and the problem may well be caught too
late to be useful.

It may be too late, but it may not. Perhaps it turns out that
complaints from end users are more effective at getting things changed
than warnings or errors messages to those who compile programs.

On the other hand, I used to see the run-time error a year or two ago,
and now I don't (but I see a compile-time warning). So perhaps it
turned out not to be effective.

-- Richard
 
R

Richard Heathfield

Keith Thompson said:
If you insist on writing unsafe code, nobody can stop you.

Oh, I see you found another one. He's in "good" company - Nick McLaren
over in csc also claims to be a regular gets() user. And he's on the
ISO C Committee, for pity's sake. Who taught these guys to program -
Schildt?

I can see why some folk might bang a gets() call into their code, much
as a bad carpenter might use a couple of nails instead of a countersunk
screw. What I *can't* fathom is their apparent lack of shame.
 
M

Malcolm McLean

Keith Thompson said:
If you insist on writing unsafe code, nobody can stop you.
Actually the compiler can. It is possible, though unfortunately not easy, to
write a compiler that handles buffer overflows on gets() correctly and
safely, by terminating the program with an error message.
It's
trivial to write your own function that works the same way gets()
does. (It's also trivial to write an equivalent of gets() that
doesn't have the same problem, something like fgets() that deletes the
trailing '\n' character.)
You haven't read the subthread. If it was trivial to use fgets() safely then
peole wouldn't have had such trouble creating correct code. Chuck Falconer's
ggets() does actually appear to work, but look at the number of conditions
in the code he posted. You'd expect at least one bug in code of that
complexity until after thorough testing.
The problem with code that does the wrong thing, as opposed to code that
invokes undefined behaviour, is that the compiler is obliged to honour a
controlled flight into terrain. If you tell the computer to read only the
first 10 digits of an 11-digit field, because you've forgotten space for the
NUL in passing MAXLINLEN to fgets(), then the computer is obliged to return
a value that is out by a factor of ten. If you tell it to overrun the buffer
by one, it may return a number out by a factor of ten, but it will probably
crash, we can write a compiler that guarantees it will crash, and it is
almost certainly better that it crash than that it calculate the wrong
value.
 
R

Richard Heathfield

Malcolm McLean said:
If it was trivial to use fgets()
safely then peole wouldn't have had such trouble creating correct
code.

It *is* trivially simple to use fgets safely and correctly. Lots of
people have trouble using fgets safely and correctly. Those two
statements do not contradict each other.

If you make it possible for programs to be written in English, you will
discover that programmers can't write in English.
 
M

Malcolm McLean

Richard Heathfield said:
Malcolm McLean said:


It *is* trivially simple to use fgets safely and correctly. Lots of
people have trouble using fgets safely and correctly. Those two
statements do not contradict each other.
I know. It is just that the list of victims is long and illustrious. The
interesting thing is that even when you point out the problems, people
usually don't get it. They figure that if they've got rid of the undefined
behaviour then the program must be OK.


Let's consider a program that calculates drug doses for diabetics. I can't
be a doctor without my little black bag, so we'll have five machines in the
bag just in case.

We enter a line.
Mr Cyril M Kornbluth, sugar level 3000

The level is calculated by another machine that was written in Visual Basic,
and works. It is in micromoles per ml.

This is the output
Machine A
Line too long, please enter only patient intials.
Machine B
This machine has performed illegal operation
Machine C
sdfgjhkeutrpitnhfc,s[n !!!!!
Machine D
Inject 6000ml insulin
Machine E
Inject 2ml insulin

Machine A was also programmed in Visual Basic and works. All the other
machines don't. Which machine do you think is the most dangerous?
If you make it possible for programs to be written in English, you will
discover that programmers can't write in English.
That is rather pessimistic, but maybe true.
 
H

Hallvard B Furuseth

Dave said:
There's no such thing as code that doesn't need to be secure.

I reserve that attitude for aspects of life where it makes more
sense, like firearms or walking on a red light (setting an example
for children).

I just don't get this obsession with gets() in particular. Yes, it's a
stupid function, but it's there. The WTF isn't gets() by itself, it's
its existence in the standard library combined with the non-existence of
a equally convenient safe variant.

The C language is chock full of ways to shoot yourself in the foot, and
compiler/OS vendors keep finding new ones - threading, ever more
aggressive optimizations, etc. If you want a language which can be
safely used without understanding something as basic as why gets() is
unsafe, don't use C.

So some throw-away program may crash if a cat walks over the keyboard.
What's the problem with that? Or it may theoretically delete all my
files, even when it contains no file system calls except on
stdin/out/err. I'm _so_ worried.
And if there was, you would find it transmogrifying into code that
*does* need to be secure at incredibly inconvenient times.

Not if you write big programs with lots of unsafe code, no. Or if you
make them unsafe for the sake of making them unsafe, which Keith seems
to think is why some people prefer gets() at times.

Maybe you just can't keep track of which of your programs are safe and
which ones aren't, so you just must avoid gets(), strcpy() without size
checking, etc, at all times. I don't see the problem. Usually I have
some throw-away programs in my /tmp/ directory, and some in my longer-
lived private ~/tmp/ too. Mostly I don't _care_ about them one way or
another. If one turns out to be useful enough to save, I clean it up a
bit first if that is necessary.
 
R

Richard Tobin

Dave Vandervies said:
There's no such thing as code that doesn't need to be secure.

Of course there is. As in any other area of life, security in
computer programs has to be weighed against other factors such as
convenience, expense, and risk. Saying all code has to be secure
makes no more sense than saying you must always lock your door.

-- Richard
 
R

Richard Heathfield

Malcolm McLean said:
I know. It is just that the list of victims is long and illustrious.
The interesting thing is that even when you point out the problems,
people usually don't get it. They figure that if they've got rid of
the undefined behaviour then the program must be OK.

Well, that's their mistake right there. The program logic must be
correct, too.
Let's consider a program that calculates drug doses for diabetics. I
can't be a doctor without my little black bag, so we'll have five
machines in the bag just in case.

We enter a line.
Mr Cyril M Kornbluth, sugar level 3000

Not the best way I've ever seen of capturing data.
The level is calculated by another machine that was written in Visual
Basic, and works.

That's a contradiction in terms.
It is in micromoles per ml.

This is the output
Machine A
Line too long, please enter only patient intials.

Get a bigger line.
Machine B
This machine has performed illegal operation

Get a better programmer.
Machine C
sdfgjhkeutrpitnhfc,s[n !!!!!
Ditto.

Machine D
Inject 6000ml insulin

Reject during programmer's testing.
Machine E
Inject 2ml insulin

Reject during programmer's testing.
Machine A was also programmed in Visual Basic and works. All the other
machines don't. Which machine do you think is the most dangerous?

All of them. Whilst fgets is trivially simple to use safely and
correctly, it is also trivially simple to use unsafely and incorrectly.
That is rather pessimistic, but maybe true.

The key lesson here, though, is nothing to do with fgets.
 
C

Chris Dollin

Malcolm McLean wrote:
Actually the compiler can.

Actually,the compiler can't.
It is possible, though unfortunately not easy,
to write a compiler that handles buffer overflows on gets() correctly and
safely, by terminating the program with an error message.

The compiler can't do that.

The /implementation/ can; the compiler can't.
 
J

JT

Charles Richmond said:
Perhaps I was *not* clear. The "gets()" function does *not*
print a warning each time it is called. Instead, *one* warning
is printed when the program is started. Somehow, using "gets()"
causes this one-time behavior to be enabled or loaded from
the "libc" library.

Ok. I don't know of any C library implementation that
behaves this way, but I'll take your word for it.

It is true on Mac OS X and on FreeBSD (and possibly the other *BSD).
The program will print this to stderr at *runtime* if it uses gets():

warning: this program uses gets(), which is unsafe.

On FreeBSD, the same warning will also be emitted at compile time.

For example, you can see people ask/complain about this
on Mac OS X's development forum:
http://lists.apple.com/archives/student-dev/2005/Mar/msg00154.html

- JT
 
K

Keith Thompson

Malcolm McLean said:
You haven't read the subthread. If it was trivial to use fgets()
safely then peole wouldn't have had such trouble creating correct
code.
[snip]

Yes, Malcolm, I have read the subthread. If *you've* read it, you
should be aware of that, since I've also posted to it.

You need to understand that if someone disagrees with you, there are
plausible explanations other than ignorance. If most people disagree
with you, you should probably spend some time thinking about the
possibility that you're wrong.
 
K

Keith Thompson

Hallvard B Furuseth said:
I reserve that attitude for aspects of life where it makes more
sense, like firearms or walking on a red light (setting an example
for children).

I just don't get this obsession with gets() in particular. Yes, it's a
stupid function, but it's there. The WTF isn't gets() by itself, it's
its existence in the standard library combined with the non-existence of
a equally convenient safe variant.

The gets() function is unique in the entire standard C library in that
there is no safe way to use it (without imposing requirements that are
outside the scope of the standard).

fgets() can be unsafe if you invoke it with invalid arguments, but
it's safe if the size argument actually reflects the size of the
buffer.

strcpy() can be unsafe if the source argument is too big, but it's
safe if you've constructed the source string in such a way that you
know it can't exceed the size of the target.

gets() is unsafe (unless you can completely control what appears in
stdin, but there's no way to do that within the language).

[...]
Not if you write big programs with lots of unsafe code, no. Or if you
make them unsafe for the sake of making them unsafe, which Keith seems
to think is why some people prefer gets() at times.

No, I don't seriously believe that. I think that some people value
minor convenience over safety, and I think that's a foolish attitude.
I object to the language standard encouraging that attitude.
Maybe you just can't keep track of which of your programs are safe and
which ones aren't, so you just must avoid gets(), strcpy() without size
checking, etc, at all times. I don't see the problem. Usually I have
some throw-away programs in my /tmp/ directory, and some in my longer-
lived private ~/tmp/ too. Mostly I don't _care_ about them one way or
another. If one turns out to be useful enough to save, I clean it up a
bit first if that is necessary.

Using gets() in a small throwaway program is not a hanging offense.
In my opinion, it's a bad habit. For myself, I prefer to avoid gets()
in all circumstances, so I don't *have* to decide whether some
particular program needs to be "safe".
 
C

Chris Torek

(The message comes out at the first *call* to gets(). A little
thought about local variables declared "static" should make clear
how this works.)

It is true on Mac OS X and on FreeBSD (and possibly the other *BSD).

Any that still use my old code, anyway. :)

Yes, this warning is my fault.
The program will print this to stderr at *runtime* if it uses gets():

warning: this program uses gets(), which is unsafe.

On FreeBSD, the same warning will also be emitted at compile time.

Once one has the luxury of a linker that will emit the compile-time
warning, one should probably remove the run-time warning. I did not
have such a linker, when I wrote this.
 
M

Malcolm McLean

Keith Thompson said:
Malcolm McLean said:
You haven't read the subthread. If it was trivial to use fgets()
safely then peole wouldn't have had such trouble creating correct
code.
[snip]

Yes, Malcolm, I have read the subthread. If *you've* read it, you
should be aware of that, since I've also posted to it.

You need to understand that if someone disagrees with you, there are
plausible explanations other than ignorance. If most people disagree
with you, you should probably spend some time thinking about the
possibility that you're wrong.
You can normally tell the difference who disagrees with you and someone who
hasn't taken on board what you are saying. The latter could be your fault as
much as theirs.

If someone simply contradicts without giving any reasons, then they are not
really disagreeing with you. A series of contractions cannot be an argument.

Often people flatter themselves that they are disagreeing when they are not.
For instance the pyramid power numberology type person may go on a TV
program with an Egyptologist, even trip him up on some matter to do with the
Great Pyramid's dimensions, and go away convinced that he has gone head to
head with the finest of the academic establishment. Of course that is
unlikely. You've got to understand the strength of a position befoe you can
answer it, not its weakness.
The argument ad numerum is a standard fallacy by the way. Not all opinions
are of equal weight.
 
G

Giorgos Keramidas

gcc does not do this. Your runtime library might.

Right :)

I'm sure you Keith know about this already, but I'm posting the
following as a FYI for the original poster. I think the original poster
was referring to something like the warning shown by the gcc invocation
below:

% $ gcc -v -ansi -pedantic -O2 -Wall foo.c
% Using built-in specs.
% Configured with: FreeBSD/i386 system compiler
% Thread model: posix
% gcc version 3.4.6 [FreeBSD] 20060825
% /usr/libexec/cc1 -quiet -v foo.c -quiet -dumpbase foo.c -ansi \
% -auxbase foo -O2 -pedantic -Wall -ansi -version -o /tmp/ccMSPbvh.s
% ignoring duplicate directory "/usr/include"
% #include "..." search starts here:
% #include <...> search starts here:
% /usr/include
% End of search list.
% GNU C version 3.4.6 [FreeBSD] 20060825 (i386-undermydesk-freebsd)
% compiled by GNU C version 3.4.6 [FreeBSD] 20060825.
% GGC heuristics: --param ggc-min-expand=63 --param ggc-min-heapsize=63371
% /usr/bin/as -v -o /tmp/ccXpaZJM.o /tmp/ccMSPbvh.s
% GNU assembler version 2.15 [FreeBSD] 2004-05-23 (i386-obrien-freebsd) \
% using BFD version 2.15 [FreeBSD] 2004-05-23
% /usr/bin/ld -V -dynamic-linker /libexec/ld-elf.so.1 /usr/lib/crt1.o \
% /usr/lib/crti.o /usr/lib/crtbegin.o -L/usr/lib /tmp/ccXpaZJM.o -lgcc \
% -lc -lgcc /usr/lib/crtend.o /usr/lib/crtn.o
% GNU ld version 2.15 [FreeBSD] 2004-05-23
% Supported emulations:
% elf_i386_fbsd
% /tmp/ccXpaZJM.o(.text+0x12): In function `main':
% : warning: warning: this program uses gets(), which is unsafe.
% $

But this is a property of the runtime C library of the particular
system. The source code for the C library contains (indented for
posting by me):

% __warn_references(gets,
% "warning: this program uses gets(), which is unsafe.");

and there are also many other functions with similar warnings in the
same C runtime library:

% __warn_references(tempnam,
% "warning: tempnam() possibly used unsafely; "
% "consider using mkstemp()");

% /*
% * XXX. Force immediate allocation of internal memory. Not used by stdio,
% * but documented historically for certain applications. Bad applications.
% */
% __warn_references(f_prealloc,
% "warning: this program uses f_prealloc(), which is not recommended.");

The GNU C compiler doesn't really have anything to do with the warning.

- Giorgos
 
K

Keith Thompson

Malcolm McLean said:
If someone simply contradicts without giving any reasons, then they
are not really disagreeing with you. A series of contractions cannot
be an argument.

<python>Yes it can.</python>

Now if you'd like to add something constructive to this discussion,
feel free to do so. I believe I've already explained why I disagree
with your assertion that most programmers cannot use fgets() safely
(or whatever it was that you asserted). If you'd like me to clarify
further why I disagree with you, you're going to have to be more
specific.
 
G

Giorgos Keramidas

In fact, if I knew how to enable such a message on my systems I *would*
enable it and remove any SW that used gets. I would especially like such
a warning enabled on the various servers I have some control over, since
I take security of servers very seriously and getting rid of
fundamentally unsafe SW is a good step towards security.

Ditto! I was only recently tempted to use library preloading and
override the standard gets() function with a function of my own, which
can be tuned to do one of the following:

1. Unconditionally convert any call to gets() to abort().

2. Unconditionally convert any call to gets() to an error message on
stderr, and a call to _exit().

3. Unconditionally convert any call to gets() to a warning message
(like the one printed by the C runtime library on FreeBSD).

4. Do nothing, but keep the 'standard' behavior of gets(), even if the
result is not optimal performance-wise.

The gets() implementation attached below seems to work for me on
FreeBSD, and I am about to test it with some larger programs on Lixux
and Solaris too in a few hours (I know this is far from a full
portability test, but it would be enough to make this modified gets()
function useful for my own work):

%%%
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define GETS_MODE_DEFAULT 0
#define GETS_MODE_WARN 1
#define GETS_MODE_EXIT 2
#define GETS_MODE_ABORT 3

char *
gets(char *buf)
{
char *options;
char *p;
int ch;
int mode;
static int warned = 0;
static char w[] =
"this program uses gets(), which is unsafe.";

mode = GETS_MODE_DEFAULT;
options = getenv("GETS_OPTIONS");
if (options != NULL) {
for (p = options; *p != '\0'; p++) {
switch (*p) {
case 'w':
case 'W':
mode = GETS_MODE_WARN;
break;
case 'e':
case 'E':
mode = GETS_MODE_EXIT;
break;
case 'a':
case 'A':
mode = GETS_MODE_ABORT;
break;
case 'd':
case 'D':
mode = GETS_MODE_DEFAULT;
break;
default:
break;
}
}
}
if (mode == GETS_MODE_ABORT) {
abort();
} else if (mode == GETS_MODE_EXIT) {
fprintf(stderr, "error: %s\n", w);
_exit(EXIT_FAILURE);
} else if (mode == GETS_MODE_WARN && warned == 0) {
fprintf(stderr, "warning: %s\n", w);
warned = 1;
}

p = buf;
while (ferror(stdin) == 0 && (ch = getchar()) != EOF && ch != '\n')
*p++ = (char)ch;
if (ferror(stdin) != 0)
return NULL;
*p = '\0';
return (buf);
}
%%%

The result so far seem to be ok, since a small test program which does
use gets() appears to fail in the expected ways when the modified gets()
is preloaded:

% $ pwd
% /home/keramida
%
% $ cat foo.c
% #include <stdio.h>
% #include <stdlib.h>
%
% int
% main(void)
% {
% char buf[100];
%
% gets(buf);
% printf("%s\n", buf);
% return EXIT_SUCCESS;
% }
%
% $ cc -fPIC -c -o gets.o gets.c
%
% $ ld -G -o libcompat.so gets.o
%
% $ echo foo bar | \
% env GETS_OPTIONS='' LD_PRELOAD=/home/keramida/libcompat.so ./a.out
% foo bar
%
% $ echo foo bar | \
% env GETS_OPTIONS='w' LD_PRELOAD=/home/keramida/libcompat.so ./a.out
% warning: this program uses gets(), which is unsafe.
% foo bar
%
% $ echo foo bar | \
% env GETS_OPTIONS='e' LD_PRELOAD=/home/keramida/libcompat.so ./a.out
% error: this program uses gets(), which is unsafe.
%
% $ echo foo bar | \
% env GETS_OPTIONS='a' LD_PRELOAD=/home/keramida/libcompat.so ./a.out
% Abort trap: 6 (core dumped)
%
% $ gdb a.out a.out.core
% [...]
% (gdb) bt
% #0 0x28151d73 in kill () at kill.S:2
% #1 0x28151d10 in __raise (s=6) at /home/build/src/lib/libc/gen/raise.c:46
% #2 0x28150a20 in abort () at /home/build/src/lib/libc/stdlib/abort.c:65
% #3 0x2807d52e in gets () from /home/keramida/libcompat.so
% #4 0x0804853e in main ()
% Current language: auto; currently asm
% (gdb)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,816
Messages
2,569,715
Members
45,503
Latest member
TraceyP38

Latest Threads

Top