summaryrefslogtreecommitdiffstats
path: root/lib/freopen-safer.c
blob: 886e3e83a7ccf5a0263d1d63e78d51320bbb3419 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/* Invoke freopen, but avoid some glitches.

   Copyright (C) 2009-2023 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

/* Written by Eric Blake.  */

#include <config.h>

#include "stdio-safer.h"

#include "attribute.h"

#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

/* GCC 13 misunderstands the dup2 trickery in this file.  */
#if 13 <= __GNUC__
# pragma GCC diagnostic ignored "-Wanalyzer-fd-leak"
#endif

/* Guarantee that FD is open; all smaller FDs must already be open.
   Return true if successful.  */
static bool
protect_fd (int fd)
{
  int value = open ("/dev/null", O_RDONLY);
  if (value != fd)
    {
      if (0 <= value)
        {
          close (value);
          errno = EBADF; /* Unexpected; this is as good as anything else.  */
        }
      return false;
    }
  return true;
}

/* Like freopen, but guarantee that reopening stdin, stdout, or stderr
   preserves the invariant that STDxxx_FILENO==fileno(stdxxx), and
   that no other stream will interfere with the standard streams.
   This is necessary because most freopen implementations will change
   the associated fd of a stream to the lowest available slot.  */

FILE *
freopen_safer (char const *name, char const *mode, FILE *f)
{
  /* Unfortunately, we cannot use the fopen_safer approach of using
     fdopen (dup_safer (fileno (freopen (cmd, mode, f)))), because we
     need to return f itself.  The implementation of freopen(NULL,m,f)
     is system-dependent, so the best we can do is guarantee that all
     lower-valued standard fds are open prior to the freopen call,
     even though this puts more pressure on open fds.  */
  bool protect_in = false;
  bool protect_out = false;
  bool protect_err = false;
  int saved_errno;

  switch (fileno (f))
    {
    default: /* -1 or not a standard stream.  */
      if (dup2 (STDERR_FILENO, STDERR_FILENO) != STDERR_FILENO)
        protect_err = true;
      FALLTHROUGH;
    case STDERR_FILENO:
      if (dup2 (STDOUT_FILENO, STDOUT_FILENO) != STDOUT_FILENO)
        protect_out = true;
      FALLTHROUGH;
    case STDOUT_FILENO:
      if (dup2 (STDIN_FILENO, STDIN_FILENO) != STDIN_FILENO)
        protect_in = true;
      FALLTHROUGH;
    case STDIN_FILENO:
      /* Nothing left to protect.  */
      break;
    }
  if (protect_in && !protect_fd (STDIN_FILENO))
    f = NULL;
  else if (protect_out && !protect_fd (STDOUT_FILENO))
    f = NULL;
  else if (protect_err && !protect_fd (STDERR_FILENO))
    f = NULL;
  else
    f = freopen (name, mode, f);
  saved_errno = errno;
  if (protect_err)
    close (STDERR_FILENO);
  if (protect_out)
    close (STDOUT_FILENO);
  if (protect_in)
    close (STDIN_FILENO);
  if (!f)
    errno = saved_errno;
  return f;
}