pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feed[PATCH] unrecognized win32 error 448 (ERROR_UNTRUSTED_MOUNT_POINT) breaks tablespaces on Win11 26200
3+ messages / 2 participants
[nested] [flat]
* [PATCH] unrecognized win32 error 448 (ERROR_UNTRUSTED_MOUNT_POINT) breaks tablespaces on Win11 26200
@ 2026-09-21 16:24 Greg Burd <greg@burd.me>
0 siblings, 1 reply; 3+ messages in thread
From: Greg Burd @ 2026-09-21 16:24 UTC (permalink / raw)
To: pgsql-hackers@lists.postgresql.org <pgsql-hackers@lists.postgresql.org>
Hi hackers,
My Windows-on-Arm buildfarm animal (unicorn: Windows 11 Pro build 26200.9457,
aarch64, MSVC 19.50, meson, cassert) currently fails 13 TAP tests on
REL_19_STABLE, and all 13 have the same cause: Windows returns error code 448
for stat() on a tablespace path, src/port/win32error.c does not know that code,
and so the error is reported as the catch-all EINVAL ("Invalid argument").
The core regression suite passes (meson check: Ok:1 Fail:0); this is confined to
tests that use tablespaces.
In the logs:
LOG: unrecognized win32 error code: 448
ERROR: could not stat directory "pg_tblspc/16387/PG_19_202609165/5": Invalid argument
STATEMENT: CREATE TABLE test1 (a int) TABLESPACE tblspc1;
Error 448 is:
#define ERROR_UNTRUSTED_MOUNT_POINT 448L (winerror.h, Windows SDK 10.0.26100)
448 -> "The path cannot be traversed because it contains an untrusted mount point"
I think this is a newer Windows hardening behavior around reparse points. Since
PostgreSQL emulates symlinks with junction points (IO_REPARSE_TAG_MOUNT_POINT)
for pg_tblspc, paths under pg_tblspc are subject to it.
So, the mapping below is worth having on its own (an unmapped Win32 code
becoming EINVAL is never useful), but I suspect the real fix for tablespaces on
recent Windows is in pgsymlink(). I would appreciate a second opinion from
people who know the Windows file-system code better than I do, particularly on
whether populating PrintName is the right move.
Happy to run experiments on this machine.
best.
-greg
Attachments:
[text/x-patch] v1-0001-win32-untrusted-mount-point.patch (3.0K, ../../JWShKT3kYU1yGqZc43ESuGyD5vfme5ciuVzwoZkW6z_85gT1dD0RF6ABBrXqRpiusxqXNbTww6yZE8VgFldFabj7BELLCKe4lq_GNiCkt6M=@burd.me/2-v1-0001-win32-untrusted-mount-point.patch)
download | inline diff:
From bb24bd82d4414cd54e37b7fdcd9cff8ed599d71f Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 20 Sep 2026 12:21:51 -0400
Subject: [PATCH] Map ERROR_UNTRUSTED_MOUNT_POINT to ENOENT on Windows
Recent Windows versions can fail path traversal with
ERROR_UNTRUSTED_MOUNT_POINT (448, "The path cannot be traversed because it
contains an untrusted mount point") when a reparse point is considered
untrusted. Since PostgreSQL emulates symbolic links with junction points,
this is reported for paths under pg_tblspc, and because the code was not in
win32error.c's translation table it fell through to the catch-all EINVAL.
That produced confusing failures such as
ERROR: could not stat directory "pg_tblspc/16388/PG_19_202609165/5": Invalid argument
ERROR: could not create directory "pg_tblspc/...": Invalid argument
and made 13 TAP tests fail on a Windows 11 (build 26200) aarch64 animal,
including recovery/014_unlogged_reinit, recovery/018_wal_optimize,
test_misc/002_tablespace, initdb/001_initdb, pg_basebackup/010_pg_basebackup,
pg_verifybackup/003_corruption and 008_untar, pg_combinebackup/002_compare_backups,
pg_checksums/002_actions, pg_waldump/001_basic, pgbench/001_pgbench_with_server,
scripts/090_reindexdb and worker_spi/002_worker_terminate.
Map it to ENOENT, consistent with the existing treatment of the other
"this path cannot be resolved" conditions ERROR_INVALID_NAME and
ERROR_CANT_RESOLVE_FILENAME (the latter added by 387803d81d6 for broken
junction points). Provide a fallback definition of the constant so that we
continue to build against older SDK and MinGW headers.
---
src/port/win32error.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/src/port/win32error.c b/src/port/win32error.c
index 11d854c7370..6a4ab80a8d4 100644
--- a/src/port/win32error.c
+++ b/src/port/win32error.c
@@ -17,6 +17,14 @@
#include "postgres_fe.h"
#endif
+/*
+ * ERROR_UNTRUSTED_MOUNT_POINT was added in newer Windows SDKs; define it here
+ * so that we still build against older SDK or MinGW headers that lack it.
+ */
+#ifndef ERROR_UNTRUSTED_MOUNT_POINT
+#define ERROR_UNTRUSTED_MOUNT_POINT 448L
+#endif
+
static const struct
{
DWORD winerr;
@@ -170,6 +178,21 @@ static const struct
},
{
ERROR_CANT_RESOLVE_FILENAME, ENOENT
+ },
+ {
+ /*
+ * ERROR_UNTRUSTED_MOUNT_POINT ("The path cannot be traversed because
+ * it contains an untrusted mount point") is reported by recent
+ * Windows versions when a reparse point is considered untrusted, for
+ * example a junction created by a non-administrative user that points
+ * outside of that user's scope. PostgreSQL uses junction points to
+ * emulate symbolic links for tablespaces, so this can be reported for
+ * paths under pg_tblspc. Map it like the other "this path cannot be
+ * resolved" errors above, so that callers using
+ * errcode_for_file_access() report a sensible condition instead of the
+ * EINVAL that an unmapped code falls back to.
+ */
+ ERROR_UNTRUSTED_MOUNT_POINT, ENOENT
}
};
--
2.54.0
^ permalink raw reply [nested|flat] 3+ messages in thread
* Re: [PATCH] unrecognized win32 error 448 (ERROR_UNTRUSTED_MOUNT_POINT) breaks tablespaces on Win11 26200
@ 2026-09-21 22:28 Bryan Green <dbryan.green@gmail.com>
parent: Greg Burd <greg@burd.me>
0 siblings, 1 reply; 3+ messages in thread
From: Bryan Green @ 2026-09-21 22:28 UTC (permalink / raw)
To: Greg Burd <greg@burd.me>; pgsql-hackers@lists.postgresql.org <pgsql-hackers@lists.postgresql.org>
On 9/21/2026 11:24 AM, Greg Burd wrote:
> Hi hackers,
>
> My Windows-on-Arm buildfarm animal (unicorn: Windows 11 Pro build 26200.9457,
> aarch64, MSVC 19.50, meson, cassert) currently fails 13 TAP tests on
> REL_19_STABLE, and all 13 have the same cause: Windows returns error code 448
> for stat() on a tablespace path, src/port/win32error.c does not know that code,
> and so the error is reported as the catch-all EINVAL ("Invalid argument").
>
> The core regression suite passes (meson check: Ok:1 Fail:0); this is confined to
> tests that use tablespaces.
>
> In the logs:
>
> LOG: unrecognized win32 error code: 448
> ERROR: could not stat directory "pg_tblspc/16387/PG_19_202609165/5": Invalid argument
> STATEMENT: CREATE TABLE test1 (a int) TABLESPACE tblspc1;
>
> Error 448 is:
>
> #define ERROR_UNTRUSTED_MOUNT_POINT 448L (winerror.h, Windows SDK 10.0.26100)
> 448 -> "The path cannot be traversed because it contains an untrusted mount point"
>
> I think this is a newer Windows hardening behavior around reparse points. Since
> PostgreSQL emulates symlinks with junction points (IO_REPARSE_TAG_MOUNT_POINT)
> for pg_tblspc, paths under pg_tblspc are subject to it.
>
> So, the mapping below is worth having on its own (an unmapped Win32 code
> becoming EINVAL is never useful), but I suspect the real fix for tablespaces on
> recent Windows is in pgsymlink(). I would appreciate a second opinion from
> people who know the Windows file-system code better than I do, particularly on
> whether populating PrintName is the right move.
>
> Happy to run experiments on this machine.
>
> best.
>
> -greg
Microsoft has changed what accounts can traverse untrusted junctions in
it's latest releases. If you search for openssh and RedirectionGuard
you will find interesting reading.
The error you are hitting is RedirectionGuard. PrintName is display
only. SubstituteName is what resolves the mount point, so populating
PrintName changes nothing.
PG does not create trusted junctions. pg_ctl launches the postmaster
with a restricted token and the backend refuses to run with admin rights
at all. So, junctions are created with a non-admin token which makes
them un-trusted by construction.
A process that has EnforceRedirectionTrust set refuses to traverse an
untrusted one and gets 448. The policy is opt-in, so it is not on by
default, and it is inherited by children.
You would need something that is launching the process that has
RedirectionGuard enabled or that it was opted in for the OS.
That is also why CI does not catch it. The GitHub Actions Windows job
runs on windows-2022, which does not enforce this. A 26200 animal with
an enforcing parent (process or OS) does.
I would check if you are running a non-standard terminal or the
Win32-Openssh 10.0.0.0-pre2 installer was used on Unicorn. That
installer makes ssh.exe enforce redirection trust. There are ways to
check if anything in the ancestor chain has that set.
--
Bryan Green
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 3+ messages in thread
* Re: [PATCH] unrecognized win32 error 448 (ERROR_UNTRUSTED_MOUNT_POINT) breaks tablespaces on Win11 26200
@ 2026-09-23 15:17 Greg Burd <greg@burd.me>
parent: Bryan Green <dbryan.green@gmail.com>
0 siblings, 0 replies; 3+ messages in thread
From: Greg Burd @ 2026-09-23 15:17 UTC (permalink / raw)
To: Bryan Green <dbryan.green@gmail.com>; +Cc: pgsql-hackers@lists.postgresql.org <pgsql-hackers@lists.postgresql.org>
> On Sep 21, 2026, at 6:28 PM, Bryan Green <dbryan.green@gmail.com> wrote:
>
> On 9/21/2026 11:24 AM, Greg Burd wrote:
>> Hi hackers,
>>
>> My Windows-on-Arm buildfarm animal (unicorn: Windows 11 Pro build 26200.9457,
>> aarch64, MSVC 19.50, meson, cassert) currently fails 13 TAP tests on
>> REL_19_STABLE, and all 13 have the same cause: Windows returns error code 448
>> for stat() on a tablespace path, src/port/win32error.c does not know that code,
>> and so the error is reported as the catch-all EINVAL ("Invalid argument").
>>
>> The core regression suite passes (meson check: Ok:1 Fail:0); this is confined to
>> tests that use tablespaces.
>>
>> In the logs:
>>
>> LOG: unrecognized win32 error code: 448
>> ERROR: could not stat directory "pg_tblspc/16387/PG_19_202609165/5": Invalid argument
>> STATEMENT: CREATE TABLE test1 (a int) TABLESPACE tblspc1;
>>
>> Error 448 is:
>>
>> #define ERROR_UNTRUSTED_MOUNT_POINT 448L (winerror.h, Windows SDK 10.0.26100)
>> 448 -> "The path cannot be traversed because it contains an untrusted mount point"
>>
>> I think this is a newer Windows hardening behavior around reparse points. Since
>> PostgreSQL emulates symlinks with junction points (IO_REPARSE_TAG_MOUNT_POINT)
>> for pg_tblspc, paths under pg_tblspc are subject to it.
>>
>> So, the mapping below is worth having on its own (an unmapped Win32 code
>> becoming EINVAL is never useful), but I suspect the real fix for tablespaces on
>> recent Windows is in pgsymlink(). I would appreciate a second opinion from
>> people who know the Windows file-system code better than I do, particularly on
>> whether populating PrintName is the right move.
>>
>> Happy to run experiments on this machine.
>>
>> best.
>>
>> -greg
>
> Microsoft has changed what accounts can traverse untrusted junctions in
> it's latest releases. If you search for openssh and RedirectionGuard
> you will find interesting reading.
Hey Bryan, thanks for replying.
Okay, thanks for the pointer. I'm not a MS expert, I just try to keep
my buildfarm animal (win11/msvc) mostly healthy.
> The error you are hitting is RedirectionGuard. PrintName is display
> only. SubstituteName is what resolves the mount point, so populating
> PrintName changes nothing.
>
> PG does not create trusted junctions. pg_ctl launches the postmaster
> with a restricted token and the backend refuses to run with admin rights
> at all. So, junctions are created with a non-admin token which makes
> them un-trusted by construction.
>
> A process that has EnforceRedirectionTrust set refuses to traverse an
> untrusted one and gets 448. The policy is opt-in, so it is not on by
> default, and it is inherited by children.
>
> You would need something that is launching the process that has
> RedirectionGuard enabled or that it was opted in for the OS.
>
> That is also why CI does not catch it. The GitHub Actions Windows job
> runs on windows-2022, which does not enforce this. A 26200 animal with
> an enforcing parent (process or OS) does.
>
> I would check if you are running a non-standard terminal or the
> Win32-Openssh 10.0.0.0-pre2 installer was used on Unicorn. That
> installer makes ssh.exe enforce redirection trust. There are ways to
> check if anything in the ancestor chain has that set.
Very helpful, thanks I'll review my setup for the animal I likely have
something wrong there that needs to be adjusted to avoid this issue.
best.
-greg
>
> --
> Bryan Green
> EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 3+ messages in thread
end of thread, other threads:[~2026-09-23 15:17 UTC | newest]
Thread overview: 3+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-09-21 16:24 [PATCH] unrecognized win32 error 448 (ERROR_UNTRUSTED_MOUNT_POINT) breaks tablespaces on Win11 26200 Greg Burd <greg@burd.me>
2026-09-21 22:28 ` Bryan Green <dbryan.green@gmail.com>
2026-09-23 15:17 ` Greg Burd <greg@burd.me>
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox