Re: [PATCH] unrecognized win32 error 448 (ERROR_UNTRUSTED_MOUNT_POINT) breaks tablespaces on Win11 26200

From: Bryan Green <dbryan(dot)green(at)gmail(dot)com>
To: Greg Burd <greg(at)burd(dot)me>, "pgsql-hackers(at)lists(dot)postgresql(dot)org" <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: Re: [PATCH] unrecognized win32 error 448 (ERROR_UNTRUSTED_MOUNT_POINT) breaks tablespaces on Win11 26200
Date: 2026-09-21 22:28:58
Message-ID: 340f649d-5b37-4161-956c-e16d42abcfa3@gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

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

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Bharath Rupireddy 2026-09-21 22:39:23 Re: Support for 8-byte TOAST values, round two
Previous Message Alex Shapalov 2026-09-21 22:16:54 Proposal: expose heavyweight lock wait start time in pg_stat_activity