Bug 21398 - freopen does not check dup3 return value, and does not return error if dup3 fails (seen when open returns fd 0, and freopen on stdin)
Summary: freopen does not check dup3 return value, and does not return error if dup3 f...
Status: RESOLVED DUPLICATE of bug 21393
Alias: None
Product: glibc
Classification: Unclassified
Component: stdio (show other bugs)
Version: 2.25
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2017-04-18 23:12 UTC by Dave Olson
Modified: 2017-05-02 12:06 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security-


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Dave Olson 2017-04-18 23:12:24 UTC
When fd 0 is closed, and the fopen in freopen gets fd 0, and dup3 is available, the dup3(0,0,0) correctly fails with EINVAL, the return status is ignored, and fd 0 is closed.  Subsequent attempts to use stdin fail.

This was observed in debian jessie with glibc 2.19, but by code inspection on glibc top of trunk in git, the bug is still present.

Here is a simple test program to show the problem:

#include <stdio.h>
#include <unistd.h>

int main(int cnt, char **args)
{
    FILE *newf;
    char buf[128], *ret;
    if(cnt != 2) {
        fprintf(stderr, "Usage: %s filename_to_freopen\n", *args);
        return 1;
    }
    (void)close(0);
    newf = freopen(args[1], "r", stdin);
    fprintf(stdout, "freopen(%s, r, stdin) returns %p\n", args[1], newf);
    fflush(stdout);
    fprintf(stdout, "ferror(%p) returns %d\n", args[1], ferror(newf));
    ret = fgets(buf, sizeof buf, stdin);
    fprintf(stdout, "fgets(%p) returns %p\n", newf, ret);
    fprintf(stdout, "2nd ferror(%p) returns %d\n", args[1], ferror(newf));
    return 0;
}

With gcc 4.9, it compiles -Wall with no warnings (on a 64bit x86_64 system, but by inspection, the 32 bit freopen.c has the same bug):

Running it on an existing file (the source for the above program):
./fbug freopen_bug.c 
freopen(freopen_bug.c, r, stdin) returns 0x7f0fdada14e0
ferror(0x7ffdaa94ad1a) returns 0
fgets(0x7f0fdada14e0) returns (nil)
2nd ferror(0x7ffdaa94ad1a) returns 1

And strace shows the issue:


16:10:40 close(0)                       = 0
16:10:40 open("freopen_bug.c", O_RDONLY) = 0
16:10:40 dup3(0, 0, 0)                  = -1 EINVAL (Invalid argument)
16:10:40 close(0)                       = 0

(no other system calls in between)
Comment 1 Andreas Schwab 2017-04-19 08:26:10 UTC
I don't think using a stream where the underlying file descriptor is closed has defined behviour.
Comment 2 Florian Weimer 2017-04-19 09:43:47 UTC
(In reply to Andreas Schwab from comment #1)
> I don't think using a stream where the underlying file descriptor is closed
> has defined behviour.

This conflicts with previous guidance I got.  Applications are supposed to be able to close descriptors used by the library (which is why we can't keep open a descriptor for /dev/urandom, for example).  Given this, it appears to be reasonable to expect that it is possible to resurrect a stream using freopen if its underlying descriptor has been closed.
Comment 3 Andreas Schwab 2017-04-19 10:32:02 UTC
In the (non-normative) application usage of close POSIX says: "Usage of close() on file descriptors STDIN_FILENO, STDOUT_FILENO, or STDERR_FILENO should immediately be followed by an operation to reopen these file descriptors."
Comment 4 Florian Weimer 2017-04-19 10:35:24 UTC
(In reply to Andreas Schwab from comment #3)
> In the (non-normative) application usage of close POSIX says: "Usage of
> close() on file descriptors STDIN_FILENO, STDOUT_FILENO, or STDERR_FILENO
> should immediately be followed by an operation to reopen these file
> descriptors."

But this doesn't say whether you can use freopen for that purpose.
Comment 5 Andreas Schwab 2017-04-19 12:03:02 UTC
freopen operates on streams, not file descriptors.
Comment 6 Dave Olson 2017-04-19 15:38:16 UTC
It's not trying to use a stream where the underlying fd is closed, it's trying to create stdin.

Think of processes that are exec'ed without fd 0 open (which is the real test case, because it's marked close on exec).

What it's trying to do is to establish a stdin for use with functions that expect to use stdin.

Even if you don't accept that as a valid use case (and I can accept the argument, even though I don't agree with it), freopen() should be returning an error, which means it needs to check for dup3 returning errors.

Right now, freopen in this situation returns what appears to be a valid FILE *, but the requested action on stdin will fail.

So the minimum is to check for dup3() errors, and then return an error from freopen().
Comment 7 Florian Weimer 2017-05-02 12:06:34 UTC
Previously reported as bug 21393.

*** This bug has been marked as a duplicate of bug 21393 ***