🌐 AI搜索 & 代理 主页
Skip to content

Commit f24af0e

Browse files
committed
Fix O_CLOEXEC flag handling in Windows port.
PostgreSQL's src/port/open.c has always set bInheritHandle = TRUE when opening files on Windows, making all file descriptors inheritable by child processes. This meant the O_CLOEXEC flag, added to many call sites by commit 1da569c (v16), was silently ignored. The original commit included a comment suggesting that our open() replacement doesn't create inheritable handles, but it was a mis- understanding of the code path. In practice, the code was creating inheritable handles in all cases. This hasn't caused widespread problems because most child processes (archive_command, COPY PROGRAM, etc.) operate on file paths passed as arguments rather than inherited file descriptors. Even if a child wanted to use an inherited handle, it would need to learn the numeric handle value, which isn't passed through our IPC mechanisms. Nonetheless, the current behavior is wrong. It violates documented O_CLOEXEC semantics, contradicts our own code comments, and makes PostgreSQL behave differently on Windows than on Unix. It also creates potential issues with future code or security auditing tools. To fix, define O_CLOEXEC to _O_NOINHERIT in master, previously used by O_DSYNC. We use different values in the back branches to preserve existing values. In pgwin32_open_handle() we set bInheritHandle according to whether O_CLOEXEC is specified, for the same atomic semantics as POSIX in multi-threaded programs that create processes. Backpatch-through: 16 Author: Bryan Green <dbryan.green@gmail.com> Co-authored-by: Thomas Munro <thomas.munro@gmail.com> (minor adjustments) Discussion: https://postgr.es/m/e2b16375-7430-4053-bda3-5d2194ff1880%40gmail.com
1 parent 7a02ac2 commit f24af0e

File tree

9 files changed

+400
-14
lines changed

9 files changed

+400
-14
lines changed

src/include/port.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,6 @@ extern bool rmtree(const char *path, bool rmtopdir);
338338
* open() and fopen() replacements to allow deletion of open files and
339339
* passing of other special options.
340340
*/
341-
#define O_DIRECT 0x80000000
342341
extern HANDLE pgwin32_open_handle(const char *, int, bool);
343342
extern int pgwin32_open(const char *, int,...);
344343
extern FILE *pgwin32_fopen(const char *, const char *);

src/include/port/win32_port.h

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -343,18 +343,15 @@ extern int _pglstat64(const char *name, struct stat *buf);
343343

344344
/*
345345
* Supplement to <fcntl.h>.
346-
* This is the same value as _O_NOINHERIT in the MS header file. This is
347-
* to ensure that we don't collide with a future definition. It means
348-
* we cannot use _O_NOINHERIT ourselves.
349-
*/
350-
#define O_DSYNC 0x0080
351-
352-
/*
353-
* Our open() replacement does not create inheritable handles, so it is safe to
354-
* ignore O_CLOEXEC. (If we were using Windows' own open(), it might be
355-
* necessary to convert this to _O_NOINHERIT.)
346+
*
347+
* We borrow bits from the high end when we have to, to avoid colliding with
348+
* the system-defined values. Our open() replacement in src/port/open.c
349+
* converts these to the equivalent CreateFile() flags, along with the ones
350+
* from fcntl.h.
356351
*/
357-
#define O_CLOEXEC 0
352+
#define O_CLOEXEC 0x04000000
353+
#define O_DIRECT 0x80000000
354+
#define O_DSYNC _O_NOINHERIT
358355

359356
/*
360357
* Supplement to <errno.h>.

src/port/open.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,23 @@ pgwin32_open_handle(const char *fileName, int fileFlags, bool backup_semantics)
7474
/* Check that we can handle the request */
7575
assert((fileFlags & ((O_RDONLY | O_WRONLY | O_RDWR) | O_APPEND |
7676
(O_RANDOM | O_SEQUENTIAL | O_TEMPORARY) |
77-
_O_SHORT_LIVED | O_DSYNC | O_DIRECT |
77+
_O_SHORT_LIVED | O_DSYNC | O_DIRECT | O_CLOEXEC |
7878
(O_CREAT | O_TRUNC | O_EXCL) | (O_TEXT | O_BINARY))) == fileFlags);
7979

8080
sa.nLength = sizeof(sa);
81-
sa.bInheritHandle = TRUE;
8281
sa.lpSecurityDescriptor = NULL;
8382

83+
/*
84+
* If O_CLOEXEC is specified, create a non-inheritable handle. Otherwise,
85+
* create an inheritable handle (the default Windows behavior).
86+
*
87+
* Note: We could instead use SetHandleInformation() after CreateFile() to
88+
* clear HANDLE_FLAG_INHERIT, but this way avoids rare leaks in
89+
* multi-threaded programs that create processes, just like POSIX
90+
* O_CLOEXEC.
91+
*/
92+
sa.bInheritHandle = !(fileFlags & O_CLOEXEC);
93+
8494
while ((h = CreateFile(fileName,
8595
/* cannot use O_RDONLY, as it == 0 */
8696
(fileFlags & O_RDWR) ? (GENERIC_WRITE | GENERIC_READ) :

src/test/modules/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ SUBDIRS = \
1414
plsample \
1515
spgist_name_ops \
1616
test_bloomfilter \
17+
test_cloexec \
1718
test_copy_callbacks \
1819
test_custom_rmgrs \
1920
test_ddl_deparse \

src/test/modules/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ subdir('plsample')
1313
subdir('spgist_name_ops')
1414
subdir('ssl_passphrase_callback')
1515
subdir('test_bloomfilter')
16+
subdir('test_cloexec')
1617
subdir('test_copy_callbacks')
1718
subdir('test_custom_rmgrs')
1819
subdir('test_ddl_deparse')
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# src/test/modules/test_cloexec/Makefile
2+
3+
# This module is for Windows only
4+
ifeq ($(PORTNAME),win32)
5+
6+
MODULE_big = test_cloexec
7+
OBJS = \
8+
$(WIN32RES) \
9+
test_cloexec.o
10+
11+
PGFILEDESC = "test_cloexec - test O_CLOEXEC flag handling"
12+
13+
# Build as a standalone program, not a shared library
14+
PROGRAM = test_cloexec
15+
override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
16+
17+
TAP_TESTS = 1
18+
19+
endif
20+
21+
ifdef USE_PGXS
22+
PG_CONFIG = pg_config
23+
PGXS := $(shell $(PG_CONFIG) --pgxs)
24+
include $(PGXS)
25+
else
26+
subdir = src/test/modules/test_cloexec
27+
top_builddir = ../../../..
28+
include $(top_builddir)/src/Makefile.global
29+
include $(top_srcdir)/contrib/contrib-global.mk
30+
endif
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# src/test/modules/test_cloexec/meson.build
2+
3+
# This test is Windows-only
4+
if host_system != 'windows'
5+
subdir_done()
6+
endif
7+
8+
test_cloexec_sources = files('test_cloexec.c')
9+
10+
test_cloexec = executable('test_cloexec',
11+
test_cloexec_sources,
12+
dependencies: [frontend_code],
13+
link_with: [pgport[''], pgcommon['']],
14+
kwargs: default_bin_args,
15+
)
16+
17+
tests += {
18+
'name': 'test_cloexec',
19+
'sd': meson.current_source_dir(),
20+
'bd': meson.current_build_dir(),
21+
'tap': {
22+
'tests': [
23+
't/001_cloexec.pl',
24+
],
25+
},
26+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Test O_CLOEXEC flag handling on Windows
2+
#
3+
# This test verifies that file handles opened with O_CLOEXEC are not
4+
# inherited by child processes, while handles opened without O_CLOEXEC
5+
# are inherited.
6+
7+
use strict;
8+
use warnings FATAL => 'all';
9+
use PostgreSQL::Test::Utils;
10+
use Test::More;
11+
use IPC::Run qw(run);
12+
use File::Spec;
13+
use Cwd 'abs_path';
14+
15+
if (!$PostgreSQL::Test::Utils::windows_os)
16+
{
17+
plan skip_all => 'test is Windows-specific';
18+
}
19+
20+
plan tests => 1;
21+
22+
my $test_prog;
23+
foreach my $dir (split(/$Config::Config{path_sep}/, $ENV{PATH}))
24+
{
25+
my $candidate = File::Spec->catfile($dir, 'test_cloexec.exe');
26+
if (-f $candidate && -x $candidate)
27+
{
28+
$test_prog = $candidate;
29+
last;
30+
}
31+
}
32+
33+
if (!$test_prog)
34+
{
35+
$test_prog = './test_cloexec.exe';
36+
}
37+
38+
if (!-f $test_prog)
39+
{
40+
BAIL_OUT("test program not found: $test_prog");
41+
}
42+
43+
note("Using test program: $test_prog");
44+
45+
my ($stdout, $stderr);
46+
my $result = run [ $test_prog ], '>', \$stdout, '2>', \$stderr;
47+
48+
note("Test program output:");
49+
note($stdout) if $stdout;
50+
51+
if ($stderr)
52+
{
53+
diag("Test program stderr:");
54+
diag($stderr);
55+
}
56+
57+
ok($result && $stdout =~ /SUCCESS.*O_CLOEXEC behavior verified/s,
58+
"O_CLOEXEC prevents handle inheritance");
59+
60+
done_testing();

0 commit comments

Comments
 (0)