Index: trunk/src/VBox/Runtime/Makefile.kmk
===================================================================
--- trunk/src/VBox/Runtime/Makefile.kmk	(revision 104283)
+++ trunk/src/VBox/Runtime/Makefile.kmk	(revision 104284)
@@ -1047,4 +1047,5 @@
 	r3/nt/fs-nt.cpp \
 	r3/nt/pathint-nt.cpp \
+	r3/nt/RTFileDelete-r3-nt.cpp \
 	r3/nt/RTFileQueryFsSizes-nt.cpp \
 	r3/nt/RTFileSetMode-r3-nt.cpp \
@@ -2647,4 +2648,5 @@
 	r3/nt/fs-nt.cpp \
 	r3/nt/pathint-nt.cpp \
+	r3/nt/RTFileDelete-r3-nt.cpp \
 	r3/nt/RTFileQueryFsSizes-nt.cpp \
 	r3/nt/RTFileSetMode-r3-nt.cpp \
Index: trunk/src/VBox/Runtime/r3/nt/RTFileDelete-r3-nt.cpp
===================================================================
--- trunk/src/VBox/Runtime/r3/nt/RTFileDelete-r3-nt.cpp	(revision 104284)
+++ trunk/src/VBox/Runtime/r3/nt/RTFileDelete-r3-nt.cpp	(revision 104284)
@@ -0,0 +1,214 @@
+/* $Id$ */
+/** @file
+ * IPRT - RTFileDelete, Native NT.
+ */
+
+/*
+ * Copyright (C) 2024 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * 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, in version 3 of the
+ * License.
+ *
+ * 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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+*   Header Files                                                                                                                 *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FILE
+#include "internal-r3-nt.h"
+
+#include <iprt/file.h>
+#include <iprt/err.h>
+
+#include "internal/fs.h"
+
+
+RTDECL(int) RTFileDelete(const char *pszFilename)
+{
+    /*
+     * Convert and normalize the path.
+     */
+    UNICODE_STRING NtName;
+    HANDLE         hRootDir;
+    int rc = RTNtPathFromWinUtf8(&NtName, &hRootDir, pszFilename);
+    if (RT_SUCCESS(rc))
+    {
+        ULONG fUnwantedFileAttribs = FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY;
+
+        /*
+         * Try open it as a file or reparse point.
+         *
+         * Note! This will succeed on directory alternate data streams, despite
+         *       the FILE_NON_DIRECTORY_FILE flag.
+         *
+         *       OTOH, it will open the reparse point even if an ADS is
+         *       specified in the path (i.e. given symlink 'foo' targeting
+         *       directory 'bar\', attempts to open 'foo::$INDEX_ALLOCATION'
+         *       will result in opening the 'foo' reparse point and any
+         *       attempts to delete it will only delete 'foo', the 'bar'
+         *       directory will be left untouched - so safe).
+         */
+        HANDLE              hPath   = RTNT_INVALID_HANDLE_VALUE;
+        IO_STATUS_BLOCK     Ios     = RTNT_IO_STATUS_BLOCK_INITIALIZER;
+        OBJECT_ATTRIBUTES   ObjAttr;
+        InitializeObjectAttributes(&ObjAttr, &NtName, 0 /*fAttrib*/, hRootDir, NULL);
+
+        ULONG fOpenOptions = FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT
+                           | FILE_SYNCHRONOUS_IO_NONALERT;
+        NTSTATUS rcNt = NtCreateFile(&hPath,
+                                     DELETE | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
+                                     &ObjAttr,
+                                     &Ios,
+                                     NULL /*AllocationSize*/,
+                                     FILE_ATTRIBUTE_NORMAL,
+                                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+                                     FILE_OPEN,
+                                     fOpenOptions,
+                                     NULL /*EaBuffer*/,
+                                     0 /*EaLength*/);
+        if (NT_SUCCESS(rcNt))
+        {
+            /*
+             * Check if it is a reparse point.
+             *
+             * DeleteFileW and MoveFileExW (on build 19040) will re-open reparse
+             * points other than symlinks, mount points (junctions?) and named
+             * pipe symlinks.  We OTOH, will just fail as that could be a
+             * potentially security problem, since unknown reparse point behaviour
+             * could be used for exploits if they work in a similar manner to the
+             * three just mentioned.
+             *
+             * To delete symbolic links, use RTSymlinkDelete.
+             *
+             * Alternative: We could model this on linux instead, and also allow
+             * this function unlink symlinks, mount points and global reparse stuff,
+             * but fail on any other reparse point.  This would make the APIs work
+             * more or less the same accross the platforms.  (Code is #if 0'ed below.)
+             *
+             * See @bugref{10632}.
+             */
+            FILE_ATTRIBUTE_TAG_INFORMATION TagInfo = {0, 0};
+            RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
+            rcNt = NtQueryInformationFile(hPath, &Ios, &TagInfo, sizeof(TagInfo), FileAttributeTagInformation);
+            if (NT_SUCCESS(rcNt))
+            {
+                if (TagInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
+                {
+#if 0
+                    if (   TagInfo.ReparseTag == IO_REPARSE_TAG_SYMLINK
+                        || TagInfo.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT
+                        || TagInfo.ReparseTag == IO_REPARSE_TAG_GLOBAL_REPARSE)
+                        fUnwantedFileAttribs = 0; /* Consider all symlinks to be files, even the ones pointing at directories. */
+                    else
+#endif
+                    {
+                        NtClose(hPath);
+                        hPath = RTNT_INVALID_HANDLE_VALUE;
+                        rcNt  = STATUS_DIRECTORY_IS_A_REPARSE_POINT;
+                        rc    = VERR_IS_A_SYMLINK;
+                    }
+                }
+            }
+            else if (rcNt == STATUS_INVALID_PARAMETER || rcNt == STATUS_NOT_IMPLEMENTED)
+                rcNt = STATUS_SUCCESS;
+            else
+            {
+                NtClose(hPath);
+                hPath = RTNT_INVALID_HANDLE_VALUE;
+            }
+        }
+        /*
+         * Retry w/o the FILE_OPEN_REPARSE_POINT if the file system returns
+         * STATUS_INVALID_PARAMETER as it could be an old one that doesn't
+         * grok the reparse point stuff.
+         */
+        else if (rcNt == STATUS_INVALID_PARAMETER)
+        {
+            fOpenOptions &= ~FILE_OPEN_REPARSE_POINT;
+            hPath = RTNT_INVALID_HANDLE_VALUE;
+            RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
+            rcNt = NtCreateFile(&hPath,
+                                DELETE | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
+                                &ObjAttr,
+                                &Ios,
+                                NULL /*AllocationSize*/,
+                                FILE_ATTRIBUTE_NORMAL,
+                                FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+                                FILE_OPEN,
+                                fOpenOptions,
+                                NULL /*EaBuffer*/,
+                                0 /*EaLength*/);
+        }
+        /* else:
+           DeleteFileW will retry opening the file w/o FILE_READ_ATTRIBUTES here when
+           the status code is STATUS_ACCESS_DENIED. We OTOH will not do that as we will
+           be querying attributes to check that it is a file and not a directory. */
+
+        if (NT_SUCCESS(rcNt))
+        {
+            /*
+             * Recheck that this is a file and not a directory or a reparse point we
+             * don't approve of.
+             *
+             * This prevents us from accidentally deleting a directory via the
+             * ::$INDEX_ALLOCATION stream, at the cost of not being able to delete
+             * any alternate data streams on directories using this API.
+             *
+             * See @bugref{10632}.
+             */
+            FILE_BASIC_INFORMATION BasicInfo = {};
+            RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
+            rcNt = NtQueryInformationFile(hPath, &Ios, &BasicInfo, sizeof(BasicInfo), FileBasicInformation);
+            if (NT_SUCCESS(rcNt))
+            {
+                if (!(BasicInfo.FileAttributes & fUnwantedFileAttribs))
+                {
+                    /*
+                     * Okay, it is a file.  Delete it.
+                     */
+                    FILE_DISPOSITION_INFORMATION DeleteInfo = { TRUE };
+                    rcNt = NtSetInformationFile(hPath, &Ios, &DeleteInfo, sizeof(DeleteInfo), FileDispositionInformation);
+                }
+                else if (BasicInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+                    rc = VERR_IS_A_DIRECTORY;
+                else
+                    rc = VERR_IS_A_SYMLINK;
+            }
+            else
+                rc = RTErrConvertFromNtStatus(rcNt);
+
+            rcNt = NtClose(hPath);
+            if (!NT_SUCCESS(rcNt) && RT_SUCCESS_NP(rc))
+                rc = RTErrConvertFromNtStatus(rcNt);
+        }
+        else if (RT_SUCCESS_NP(rc))
+            rc = RTErrConvertFromNtStatus(rcNt);
+        RTNtPathFree(&NtName, &hRootDir);
+    }
+    return rc;
+}
+
Index: trunk/src/VBox/Runtime/r3/win/fileio-win.cpp
===================================================================
--- trunk/src/VBox/Runtime/r3/win/fileio-win.cpp	(revision 104283)
+++ trunk/src/VBox/Runtime/r3/win/fileio-win.cpp	(revision 104284)
@@ -1486,5 +1486,5 @@
 /* RTFileQueryFsSizes is implemented by ../nt/RTFileQueryFsSizes-nt.cpp */
 
-
+#if 0 /* RTFileDelete is implemented by ../nt/RTFileDelete-r3-nt.cpp */
 RTR3DECL(int)  RTFileDelete(const char *pszFilename)
 {
@@ -1500,4 +1500,5 @@
     return rc;
 }
+#endif
 
 
