Coverage Report

Created: 2025-06-13 06:06

/src/postgres/src/backend/libpq/be-secure-common.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * be-secure-common.c
4
 *
5
 * common implementation-independent SSL support code
6
 *
7
 * While be-secure.c contains the interfaces that the rest of the
8
 * communications code calls, this file contains support routines that are
9
 * used by the library-specific implementations such as be-secure-openssl.c.
10
 *
11
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
12
 * Portions Copyright (c) 1994, Regents of the University of California
13
 *
14
 * IDENTIFICATION
15
 *    src/backend/libpq/be-secure-common.c
16
 *
17
 *-------------------------------------------------------------------------
18
 */
19
20
#include "postgres.h"
21
22
#include <sys/stat.h>
23
#include <unistd.h>
24
25
#include "common/percentrepl.h"
26
#include "common/string.h"
27
#include "libpq/libpq.h"
28
#include "storage/fd.h"
29
30
/*
31
 * Run ssl_passphrase_command
32
 *
33
 * prompt will be substituted for %p.  is_server_start determines the loglevel
34
 * of error messages.
35
 *
36
 * The result will be put in buffer buf, which is of size size.  The return
37
 * value is the length of the actual result.
38
 */
39
int
40
run_ssl_passphrase_command(const char *prompt, bool is_server_start, char *buf, int size)
41
0
{
42
0
  int     loglevel = is_server_start ? ERROR : LOG;
43
0
  char     *command;
44
0
  FILE     *fh;
45
0
  int     pclose_rc;
46
0
  size_t    len = 0;
47
48
0
  Assert(prompt);
49
0
  Assert(size > 0);
50
0
  buf[0] = '\0';
51
52
0
  command = replace_percent_placeholders(ssl_passphrase_command, "ssl_passphrase_command", "p", prompt);
53
54
0
  fh = OpenPipeStream(command, "r");
55
0
  if (fh == NULL)
56
0
  {
57
0
    ereport(loglevel,
58
0
        (errcode_for_file_access(),
59
0
         errmsg("could not execute command \"%s\": %m",
60
0
            command)));
61
0
    goto error;
62
0
  }
63
64
0
  if (!fgets(buf, size, fh))
65
0
  {
66
0
    if (ferror(fh))
67
0
    {
68
0
      explicit_bzero(buf, size);
69
0
      ereport(loglevel,
70
0
          (errcode_for_file_access(),
71
0
           errmsg("could not read from command \"%s\": %m",
72
0
              command)));
73
0
      goto error;
74
0
    }
75
0
  }
76
77
0
  pclose_rc = ClosePipeStream(fh);
78
0
  if (pclose_rc == -1)
79
0
  {
80
0
    explicit_bzero(buf, size);
81
0
    ereport(loglevel,
82
0
        (errcode_for_file_access(),
83
0
         errmsg("could not close pipe to external command: %m")));
84
0
    goto error;
85
0
  }
86
0
  else if (pclose_rc != 0)
87
0
  {
88
0
    char     *reason;
89
90
0
    explicit_bzero(buf, size);
91
0
    reason = wait_result_to_str(pclose_rc);
92
0
    ereport(loglevel,
93
0
        (errcode_for_file_access(),
94
0
         errmsg("command \"%s\" failed",
95
0
            command),
96
0
         errdetail_internal("%s", reason)));
97
0
    pfree(reason);
98
0
    goto error;
99
0
  }
100
101
  /* strip trailing newline and carriage return */
102
0
  len = pg_strip_crlf(buf);
103
104
0
error:
105
0
  pfree(command);
106
0
  return len;
107
0
}
108
109
110
/*
111
 * Check permissions for SSL key files.
112
 */
113
bool
114
check_ssl_key_file_permissions(const char *ssl_key_file, bool isServerStart)
115
0
{
116
0
  int     loglevel = isServerStart ? FATAL : LOG;
117
0
  struct stat buf;
118
119
0
  if (stat(ssl_key_file, &buf) != 0)
120
0
  {
121
0
    ereport(loglevel,
122
0
        (errcode_for_file_access(),
123
0
         errmsg("could not access private key file \"%s\": %m",
124
0
            ssl_key_file)));
125
0
    return false;
126
0
  }
127
128
  /* Key file must be a regular file */
129
0
  if (!S_ISREG(buf.st_mode))
130
0
  {
131
0
    ereport(loglevel,
132
0
        (errcode(ERRCODE_CONFIG_FILE_ERROR),
133
0
         errmsg("private key file \"%s\" is not a regular file",
134
0
            ssl_key_file)));
135
0
    return false;
136
0
  }
137
138
  /*
139
   * Refuse to load key files owned by users other than us or root, and
140
   * require no public access to the key file.  If the file is owned by us,
141
   * require mode 0600 or less.  If owned by root, require 0640 or less to
142
   * allow read access through either our gid or a supplementary gid that
143
   * allows us to read system-wide certificates.
144
   *
145
   * Note that roughly similar checks are performed in
146
   * src/interfaces/libpq/fe-secure-openssl.c so any changes here may need
147
   * to be made there as well.  The environment is different though; this
148
   * code can assume that we're not running as root.
149
   *
150
   * Ideally we would do similar permissions checks on Windows, but it is
151
   * not clear how that would work since Unix-style permissions may not be
152
   * available.
153
   */
154
0
#if !defined(WIN32) && !defined(__CYGWIN__)
155
0
  if (buf.st_uid != geteuid() && buf.st_uid != 0)
156
0
  {
157
0
    ereport(loglevel,
158
0
        (errcode(ERRCODE_CONFIG_FILE_ERROR),
159
0
         errmsg("private key file \"%s\" must be owned by the database user or root",
160
0
            ssl_key_file)));
161
0
    return false;
162
0
  }
163
164
0
  if ((buf.st_uid == geteuid() && buf.st_mode & (S_IRWXG | S_IRWXO)) ||
165
0
    (buf.st_uid == 0 && buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)))
166
0
  {
167
0
    ereport(loglevel,
168
0
        (errcode(ERRCODE_CONFIG_FILE_ERROR),
169
0
         errmsg("private key file \"%s\" has group or world access",
170
0
            ssl_key_file),
171
0
         errdetail("File must have permissions u=rw (0600) or less if owned by the database user, or permissions u=rw,g=r (0640) or less if owned by root.")));
172
0
    return false;
173
0
  }
174
0
#endif
175
176
0
  return true;
177
0
}