Sandboxie/Sandboxie/core/dll/file.c

7553 lines
221 KiB
C

/*
* Copyright 2004-2020 Sandboxie Holdings, LLC
* Copyright 2020-2023 David Xanatos, xanasoft.com
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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/>.
*/
//---------------------------------------------------------------------------
// File
//---------------------------------------------------------------------------
#define NOGDI
#include "dll.h"
#include "obj.h"
#include "handle.h"
#include <stdio.h>
#include <dbt.h>
#include "core/svc/FileWire.h"
#include "core/svc/InteractiveWire.h"
#include "debug.h"
//---------------------------------------------------------------------------
// Defines
//---------------------------------------------------------------------------
#define FILE_DENIED_ACCESS ~( \
STANDARD_RIGHTS_READ | GENERIC_READ | SYNCHRONIZE | READ_CONTROL | \
FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | \
GENERIC_EXECUTE | FILE_EXECUTE)
#define DIRECTORY_JUNCTION_ACCESS ( \
GENERIC_ALL | GENERIC_WRITE | MAXIMUM_ALLOWED | \
FILE_APPEND_DATA | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES)
#define DELETE_MARK_LOW 0xDEAD44A0
#define DELETE_MARK_HIGH 0x01B01234
#define IS_DELETE_MARK(p_large_integer) \
((p_large_integer)->HighPart == DELETE_MARK_HIGH && \
(p_large_integer)->LowPart == DELETE_MARK_LOW)
#define TYPE_DIRECTORY FILE_DIRECTORY_FILE
#define TYPE_FILE FILE_NON_DIRECTORY_FILE
#define TYPE_DELETED FILE_DELETE_ON_CLOSE
#define TYPE_READ_ONLY FILE_RESERVE_OPFILTER
#define TYPE_SYSTEM FILE_OPEN_FOR_FREE_SPACE_QUERY
#define TYPE_REPARSE_POINT FILE_OPEN_REPARSE_POINT
#define OBJECT_ATTRIBUTES_ATTRIBUTES \
(ObjectAttributes \
? ObjectAttributes->Attributes | OBJ_CASE_INSENSITIVE \
: 0)
#ifdef _WIN64
#define PROXY_PIPE_MASK 0xFFFFFFFFFFFFFF00
#else
#define PROXY_PIPE_MASK 0xFFFFFF00
#endif
#define FGN_IS_BOXED_PATH 0x0001
#define FGN_TRAILING_BACKSLASH 0x0002
#define FGN_NETWORK_SHARE 0x0004
#define FGN_REPARSED_OPEN_PATH 0x0100
#define FGN_REPARSED_CLOSED_PATH 0x0200
#define FGN_REPARSED_WRITE_PATH 0x0400
#define NO_RELOCATION ((PUNICODE_STRING)-1)
#ifndef _WIN64
#define WOW64_FS_REDIR
#endif ! _WIN64
//---------------------------------------------------------------------------
// Structures and Types
//---------------------------------------------------------------------------
struct _FILE_DRIVE;
struct _FILE_LINK;
struct _FILE_GUID;
typedef struct _FILE_LINK FILE_LINK;
typedef struct _FILE_DRIVE FILE_DRIVE;
typedef struct _FILE_GUID FILE_GUID;
//---------------------------------------------------------------------------
// Functions
//---------------------------------------------------------------------------
static ULONG File_FindBoxPrefixLength(const WCHAR* CopyPath);
NTSTATUS File_GetCopyPath(WCHAR *TruePath, WCHAR **OutCopyPath);
NTSTATUS File_GetTruePath(WCHAR *CopyPath, WCHAR **OutTruePath);
WCHAR* File_FindSnapshotPath(WCHAR* CopyPath);
SBIEDLL_EXPORT NTSTATUS File_GetName(
HANDLE RootDirectory, UNICODE_STRING *ObjectName,
WCHAR **OutTruePath, WCHAR **OutCopyPath, ULONG *OutFlags);
static WCHAR *File_GetName_TranslateSymlinks(
THREAD_DATA *TlsData, const WCHAR *objname_buf, ULONG objname_len,
BOOLEAN *translated);
static WCHAR *File_GetName_ExpandShortNames(
THREAD_DATA *TlsData, WCHAR *Path);
static BOOLEAN File_GetName_ConvertLinks(
THREAD_DATA *TlsData, WCHAR **OutTruePath, BOOLEAN ConvertWow64Link);
static void File_GetName_FixTruePrefix(
THREAD_DATA *TlsData,
WCHAR **OutTruePath, ULONG *InOutLength,
ULONG old_prefix_len,
const WCHAR *new_prefix, ULONG new_prefix_len);
#ifdef WOW64_FS_REDIR
static ULONG File_GetName_SkipWow64Link(const WCHAR *name);
#endif WOW64_FS_REDIR
static NTSTATUS File_GetName_FromFileId(
OBJECT_ATTRIBUTES *ObjectAttributes,
WCHAR **OutTruePath, WCHAR **OutCopyPath);
static ULONG File_MatchPath(const WCHAR *path, ULONG *FileFlags);
static ULONG File_MatchPath2(const WCHAR *path, ULONG *FileFlags, BOOLEAN bCheckObjectExists, BOOLEAN bMonitorLog);
static NTSTATUS File_NtOpenFile(
HANDLE *FileHandle,
ACCESS_MASK DesiredAccess,
OBJECT_ATTRIBUTES *ObjectAttributes,
IO_STATUS_BLOCK *IoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions);
static NTSTATUS File_NtCreateFile(
HANDLE *FileHandle,
ACCESS_MASK DesiredAccess,
OBJECT_ATTRIBUTES *ObjectAttributes,
IO_STATUS_BLOCK *IoStatusBlock,
LARGE_INTEGER *AllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG CreateDisposition,
ULONG CreateOptions,
void *EaBuffer,
ULONG EaLength);
static NTSTATUS File_NtCreateFileImpl(
HANDLE *FileHandle,
ACCESS_MASK DesiredAccess,
OBJECT_ATTRIBUTES *ObjectAttributes,
IO_STATUS_BLOCK *IoStatusBlock,
LARGE_INTEGER *AllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG CreateDisposition,
ULONG CreateOptions,
void *EaBuffer,
ULONG EaLength);
static NTSTATUS File_CheckCreateParameters(
ACCESS_MASK DesiredAccess, ULONG CreateDisposition,
ULONG CreateOptions, ULONG FileType);
static NTSTATUS File_GetFileType(
OBJECT_ATTRIBUTES *ObjectAttributes, BOOLEAN IsWritePath,
ULONG *FileType, BOOLEAN *IsEmpty);
static BOOLEAN File_CheckDeletedParent(WCHAR *CopyPath);
static NTSTATUS File_CreatePath(WCHAR *TruePath, WCHAR *CopyPath);
static NTSTATUS File_MigrateFile(
const WCHAR *TruePath, const WCHAR *CopyPath,
BOOLEAN IsWritePath, BOOLEAN WithContents);
static NTSTATUS File_MigrateJunction(
const WCHAR *TruePath, const WCHAR *CopyPath,
BOOLEAN IsWritePath);
static NTSTATUS File_CopyShortName(
const WCHAR *TruePath, const WCHAR *CopyPath);
static BOOLEAN File_AdjustShortName(
const WCHAR *TruePath, const WCHAR *CopyPath, HANDLE FileHandle);
static NTSTATUS File_SetCreateTime(HANDLE FileHandle, const WCHAR *CopyPath);
static NTSTATUS File_MarkDeleted(HANDLE FileHandle, const WCHAR *CopyPath);
static NTSTATUS File_QueryFullAttributesDirectoryFile(
const WCHAR *TruePath, FILE_NETWORK_OPEN_INFORMATION *FileInformation);
static ULONG File_CheckDepthForIsWritePath(const WCHAR *TruePath);
static NTSTATUS File_NtQueryAttributesFile(
OBJECT_ATTRIBUTES *ObjectAttributes,
FILE_BASIC_INFORMATION *FileInformation);
static NTSTATUS File_NtQueryFullAttributesFile(
OBJECT_ATTRIBUTES *ObjectAttributes,
FILE_NETWORK_OPEN_INFORMATION *FileInformation);
static NTSTATUS File_NtQueryFullAttributesFileImpl(
OBJECT_ATTRIBUTES *ObjectAttributes,
FILE_NETWORK_OPEN_INFORMATION *FileInformation);
static NTSTATUS File_NtQueryInformationFile(
HANDLE FileHandle,
IO_STATUS_BLOCK *IoStatusBlock,
void *FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass);
static ULONG File_GetFinalPathNameByHandleW(
HANDLE hFile, WCHAR *lpszFilePath, ULONG cchFilePath, ULONG dwFlags);
static WCHAR *File_GetFinalPathNameByHandleW_2(
WCHAR *TruePath, ULONG dwFlags);
static WCHAR *File_GetFinalPathNameByHandleW_3(
WCHAR *TruePath, ULONG TruePath_len);
static NTSTATUS File_NtSetInformationFile(
HANDLE FileHandle,
IO_STATUS_BLOCK *IoStatusBlock,
void *FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass);
static NTSTATUS File_SetAttributes(
HANDLE FileHandle, const WCHAR *CopyPath,
FILE_BASIC_INFORMATION *Information);
NTSTATUS File_SetDisposition(
HANDLE FileHandle, IO_STATUS_BLOCK *IoStatusBlock,
void *FileInformation, ULONG Length, FILE_INFORMATION_CLASS FileInformationClass);
static NTSTATUS File_NtDeleteFile(OBJECT_ATTRIBUTES *ObjectAttributes);
static NTSTATUS File_NtDeleteFileImpl(OBJECT_ATTRIBUTES *ObjectAttributes);
static NTSTATUS File_RenameFile(
HANDLE FileHandle, FILE_RENAME_INFORMATION *info);
static BOOLEAN File_RecordRecover(HANDLE FileHandle, const WCHAR *TruePath);
static NTSTATUS File_SetReparsePoint(
HANDLE FileHandle, PREPARSE_DATA_BUFFER Data, ULONG DataLen);
NTSTATUS File_GetFileName(HANDLE FileHandle, ULONG NameLen, WCHAR *NameBuf);
//---------------------------------------------------------------------------
static P_NtOpenFile __sys_NtOpenFile = NULL;
P_NtCreateFile __sys_NtCreateFile = NULL;
static P_NtQueryAttributesFile __sys_NtQueryAttributesFile = NULL;
static P_NtQueryFullAttributesFile __sys_NtQueryFullAttributesFile = NULL;
static P_NtQueryInformationFile __sys_NtQueryInformationFile = NULL;
P_GetFinalPathNameByHandle __sys_GetFinalPathNameByHandleW = NULL;
P_NtQueryDirectoryFile __sys_NtQueryDirectoryFile = NULL;
static P_NtQueryDirectoryFileEx __sys_NtQueryDirectoryFileEx = NULL;
static P_NtSetInformationFile __sys_NtSetInformationFile = NULL;
static P_NtDeleteFile __sys_NtDeleteFile = NULL;
P_NtClose __sys_NtClose = NULL;
static P_NtCreateNamedPipeFile __sys_NtCreateNamedPipeFile = NULL;
static P_NtCreateMailslotFile __sys_NtCreateMailslotFile = NULL;
static P_NtReadFile __sys_NtReadFile = NULL;
static P_NtWriteFile __sys_NtWriteFile = NULL;
static P_NtFsControlFile __sys_NtFsControlFile = NULL;
P_NtDeviceIoControlFile __sys_NtDeviceIoControlFile = NULL;
static P_RtlGetCurrentDirectory_U __sys_RtlGetCurrentDirectory_U = NULL;
static P_RtlSetCurrentDirectory_U __sys_RtlSetCurrentDirectory_U = NULL;
static P_RtlGetFullPathName_U __sys_RtlGetFullPathName_U = NULL;
static P_RtlGetFullPathName_UEx __sys_RtlGetFullPathName_UEx = NULL;
static P_NtQueryVolumeInformationFile
__sys_NtQueryVolumeInformationFile
= NULL;
//---------------------------------------------------------------------------
// Variables
//---------------------------------------------------------------------------
// Windows 2000 and Windows XP name for the LanmanRedirector device
static const WCHAR *File_Redirector = L"\\device\\lanmanredirector\\";
static const ULONG File_RedirectorLen = 25;
// Windows Vista name for the LanmanRedirector device
static const WCHAR *File_MupRedir = L"\\device\\mup\\;lanmanredirector\\";
static const ULONG File_MupRedirLen = 30;
static const WCHAR *File_DfsClientRedir = L"\\device\\mup\\dfsclient\\";
static const ULONG File_DfsClientRedirLen = 22;
static const WCHAR *File_HgfsRedir = L"\\device\\mup\\;hgfs\\";
static const ULONG File_HgfsRedirLen = 18;
const WCHAR *File_Mup = L"\\device\\mup\\";
static const ULONG File_MupLen = 12;
const WCHAR *File_BQQB = L"\\??\\";
static const ULONG _DeviceLen = 8;
static const WCHAR *_Share = L"\\share\\";
static const ULONG _ShareLen = 7;
static const WCHAR *_Drive = L"\\drive\\";
static const ULONG _DriveLen = 7;
static const WCHAR *_User = L"\\user";
static const ULONG _UserLen = 5;
static const WCHAR *_UserAll = L"\\user\\all";
static const ULONG _UserAllLen = 9;
static const WCHAR *_UserCurrent = L"\\user\\current";
static const ULONG _UserCurrentLen = 13;
static const WCHAR *_UserPublic = L"\\user\\public";
static const ULONG _UserPublicLen = 12;
#ifdef WOW64_FS_REDIR
static WCHAR *File_Wow64System32 = NULL;
static ULONG File_Wow64System32Len = 0;
static WCHAR *File_Wow64SysNative = NULL;
static ULONG File_Wow64SysNativeLen = 0;
static FILE_LINK *File_Wow64FileLink = NULL;
static void *File_Wow64DisableWow64FsRedirection = NULL;
static void *File_Wow64RevertWow64FsRedirection = NULL;
#endif WOW64_FS_REDIR
//static WCHAR *File_SysVolume = NULL;
//static ULONG File_SysVolumeLen = 0;
static WCHAR *File_AllUsers = NULL;
static ULONG File_AllUsersLen = 0;
static WCHAR *File_CurrentUser = NULL;
static ULONG File_CurrentUserLen = 0;
static WCHAR *File_PublicUser = NULL;
static ULONG File_PublicUserLen = 0;
static BOOLEAN File_DriveAddSN = FALSE;
BOOLEAN File_Delete_v2 = FALSE;
static BOOLEAN File_NoReparse = FALSE;
static WCHAR *File_AltBoxPath = NULL;
static ULONG File_AltBoxPathLen = 0;
//---------------------------------------------------------------------------
// File (other modules)
//---------------------------------------------------------------------------
#include <winioctl.h>
#include "file_link.c"
#include "file_pipe.c"
#include "file_del.c"
#include "file_snapshots.c"
#include "file_dir.c"
#include "file_recovery.c"
#include "file_misc.c"
#include "file_copy.c"
#include "file_init.c"
//---------------------------------------------------------------------------
// File_FindBoxPrefixLength
//---------------------------------------------------------------------------
_FX ULONG File_FindBoxPrefixLength(const WCHAR* CopyPath)
{
ULONG length = wcslen(CopyPath);
ULONG prefixLen = 0;
if (length >= Dll_BoxFilePathLen && 0 == Dll_NlsStrCmp(CopyPath, Dll_BoxFilePath, Dll_BoxFilePathLen))
prefixLen = Dll_BoxFilePathLen;
if (File_AltBoxPath && length >= File_AltBoxPathLen && 0 == Dll_NlsStrCmp(CopyPath, File_AltBoxPath, File_AltBoxPathLen))
prefixLen = File_AltBoxPathLen;
return prefixLen;
}
//---------------------------------------------------------------------------
// File_GetCopyPathImpl
//---------------------------------------------------------------------------
_FX NTSTATUS File_GetCopyPathImpl(WCHAR* TruePath, WCHAR **OutCopyPath, ULONG *OutFlags, WCHAR* snapshot_id, BOOLEAN have_trailing_backslash, BOOLEAN* p_add_trailing_backslash)
{
THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
ULONG length;
WCHAR* name;
const FILE_DRIVE *drive;
ULONG PrefixLength;
length = wcslen(TruePath);
name = Dll_GetTlsNameBuffer(
TlsData, COPY_NAME_BUFFER, Dll_BoxFilePathLen + length);
*OutCopyPath = name;
wmemcpy(name, Dll_BoxFilePath, Dll_BoxFilePathLen);
name += Dll_BoxFilePathLen;
//
// if we requested real paths, re add the snapshot prefix
//
if (snapshot_id && *snapshot_id) {
*name++ = L'\\';
wmemcpy(name, File_Snapshot_Prefix, File_Snapshot_PrefixLen);
name += File_Snapshot_PrefixLen;
ULONG len = wcslen(snapshot_id);
wmemcpy(name, snapshot_id, len);
name += len;
}
//
// if the true path points to a remote share or mapped drive,
// convert that to box the portable form "\share\computer\folder"
//
PrefixLength = 0;
if (length >= File_RedirectorLen && _wcsnicmp(TruePath, File_Redirector, File_RedirectorLen) == 0)
PrefixLength = File_RedirectorLen;
else if (length >= File_DfsClientRedirLen && _wcsnicmp(TruePath, File_DfsClientRedir, File_DfsClientRedirLen) == 0)
PrefixLength = File_DfsClientRedirLen;
else if (length >= File_HgfsRedirLen && _wcsnicmp(TruePath, File_HgfsRedir, File_HgfsRedirLen) == 0)
PrefixLength = File_HgfsRedirLen;
else if (length >= File_MupRedirLen && _wcsnicmp(TruePath, File_MupRedir, File_MupRedirLen) == 0)
PrefixLength = File_MupRedirLen;
if (PrefixLength) {
WCHAR *ptr = TruePath + PrefixLength;
if (*ptr == L';') {
ptr = wcschr(ptr, L'\\');
if (! ptr)
return STATUS_BAD_INITIAL_PC;
++ptr;
}
wmemcpy(name, _Share, _ShareLen);
name += _ShareLen;
length = wcslen(ptr);
wmemcpy(name, ptr, length);
if (OutFlags)
*OutFlags |= FGN_NETWORK_SHARE;
// does this next section really need to be different than above?
} else if (length >= File_MupLen &&
_wcsnicmp(TruePath, File_Mup, File_MupLen) == 0) {
WCHAR *ptr = TruePath + File_MupLen;
if (*ptr == L';') // like \Device\Mup\;RdpDr;:2\...
return STATUS_BAD_INITIAL_PC;
ptr = wcschr(ptr, L'\\');
if (File_IsPipeSuffix(ptr))
return STATUS_BAD_INITIAL_PC;
wmemcpy(name, _Share, _ShareLen);
name += _ShareLen;
length -= File_MupLen;
wmemcpy(name, TruePath + File_MupLen, length);
if (OutFlags)
*OutFlags |= FGN_NETWORK_SHARE;
}
//
// if the true path begins with the full path to the home folder
// for the AllUsers or for the current user, then we translate
// the copy path to the box portable form "\user\all" or
// "\user\current", respectively
//
else if (File_AllUsersLen && length >= File_AllUsersLen &&
0 == Dll_NlsStrCmp(
TruePath, File_AllUsers, File_AllUsersLen))
{
wmemcpy(name, _UserAll, _UserAllLen);
name += _UserAllLen;
length -= File_AllUsersLen;
wmemcpy(name, TruePath + File_AllUsersLen, length);
}
else if (File_CurrentUserLen && length >= File_CurrentUserLen &&
0 == Dll_NlsStrCmp(
TruePath, File_CurrentUser, File_CurrentUserLen))
{
wmemcpy(name, _UserCurrent, _UserCurrentLen);
name += _UserCurrentLen;
length -= File_CurrentUserLen;
wmemcpy(name, TruePath + File_CurrentUserLen, length);
}
else if (File_PublicUserLen && length >= File_PublicUserLen &&
0 == Dll_NlsStrCmp(
TruePath, File_PublicUser, File_PublicUserLen))
{
wmemcpy(name, _UserPublic, _UserPublicLen);
name += _UserPublicLen;
length -= File_PublicUserLen;
wmemcpy(name, TruePath + File_PublicUserLen, length);
}
//
// otherwise, if the true path begins with the NT path for one of
// the known DosDevices drives, then translate to the box portable
// form "\drive\x"
//
else {
ULONG drive_len;
drive = File_GetDriveForPath(TruePath, length);
if (drive)
drive_len = drive->len;
else
drive = File_GetDriveForUncPath(TruePath, length, &drive_len);
if (drive) {
WCHAR drive_letter = drive->letter;
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
wmemcpy(name, _Drive, _DriveLen);
name += _DriveLen;
*name = drive_letter;
++name;
if (File_DriveAddSN && *drive->sn) {
*name = L'~';
++name;
wcscpy(name, drive->sn);
name += 9;
}
*name = L'\0';
if (length == drive_len) {
//
// in the special case of a request to open the
// volume device itself, rather than any file within
// the device, we return a special status code
//
if (! have_trailing_backslash)
return STATUS_BAD_INITIAL_PC;
//
// otherwise, caller must want to open the root
// directory of the device, so remember to add the
// trailing backslash before we're done
//
if (p_add_trailing_backslash) *p_add_trailing_backslash = TRUE;
}
length -= drive_len;
wmemcpy(name, TruePath + drive_len, length);
} else {
//
// if we couldn't find any matching logical drive, then
// we return STATUS_BAD_INITIAL_PC so this DLL does not
// try any further sandboxing. (But the driver will still
// block any attempt to access disk devices.)
//
return STATUS_BAD_INITIAL_PC;
}
}
//
// null-terminate the copy path, and add the missing trailing
// backslash to the true path, if there was one
//
name += length;
*name = L'\0';
return STATUS_SUCCESS;
}
//---------------------------------------------------------------------------
// File_GetCopyPath
//---------------------------------------------------------------------------
_FX NTSTATUS File_GetCopyPath(WCHAR* TruePath, WCHAR **OutCopyPath)
{
return File_GetCopyPathImpl(TruePath, OutCopyPath, NULL, NULL, FALSE, NULL);
}
//---------------------------------------------------------------------------
// File_GetTruePathImpl
//---------------------------------------------------------------------------
_FX NTSTATUS File_GetTruePathImpl(ULONG* p_length, WCHAR **OutTruePath, ULONG *OutFlags, BOOLEAN* p_is_boxed_path, BOOLEAN no_relocation, WCHAR* snapshot_id, BOOLEAN* p_convert_links_again)
{
THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
WCHAR* name;
const FILE_DRIVE *drive;
check_sandbox_prefix:
if (*p_length >= Dll_BoxFilePathLen &&
0 == Dll_NlsStrCmp(
*OutTruePath, Dll_BoxFilePath, Dll_BoxFilePathLen))
{
*OutTruePath += Dll_BoxFilePathLen;
*p_length -= Dll_BoxFilePathLen;
if (! *p_length) {
//
// caller specified just the sandbox prefix
//
*OutTruePath = NULL;
return STATUS_BAD_INITIAL_PC;
}
if (OutFlags)
*OutFlags |= FGN_IS_BOXED_PATH;
*p_is_boxed_path = TRUE;
}
if (File_AltBoxPath &&
*p_length >= File_AltBoxPathLen &&
0 == Dll_NlsStrCmp(
*OutTruePath, File_AltBoxPath, File_AltBoxPathLen))
{
*OutTruePath += File_AltBoxPathLen;
*p_length -= File_AltBoxPathLen;
if (! *p_length) {
//
// caller specified just the sandbox prefix
//
*OutTruePath = NULL;
return STATUS_BAD_INITIAL_PC;
}
if (OutFlags)
*OutFlags |= FGN_IS_BOXED_PATH;
*p_is_boxed_path = TRUE;
}
//
// If its a sandboxed file, check if its in the current image or in a snapshot
// If its in a snapshot remove the snapshot prefix
//
if (p_is_boxed_path) {
if (*p_length >= 10 && 0 == Dll_NlsStrCmp(*OutTruePath + 1, File_Snapshot_Prefix, File_Snapshot_PrefixLen))
{
WCHAR* path = wcschr(*OutTruePath + 1 + File_Snapshot_PrefixLen, L'\\');
if (path == NULL) {
//
// caller specified just the sandbox snapshot prefix, or the path is to long
//
*OutTruePath = NULL;
return STATUS_BAD_INITIAL_PC;
}
if (no_relocation) {
ULONG len = (ULONG)(path - (*OutTruePath + 1 + File_Snapshot_PrefixLen));
if (len < FILE_MAX_SNAPSHOT_ID) {
wmemcpy(snapshot_id, *OutTruePath + 1 + File_Snapshot_PrefixLen, len);
snapshot_id[len] = L'\0';
}
}
*p_length -= (ULONG)(path - *OutTruePath);
*OutTruePath = path;
}
}
//
// the true path may now begin with "\drive\x", for instance,
// if the process specified a RootDirectory handle that leads
// inside the box. we have to change this box convention to
// full NT path of the drive letter. a later section of code
// will change it back to \drive\x for the copy path.
//
// note that we temporarily use the COPY_NAME_BUFFER here, but
// that's ok because it hasn't been initialized yet
//
if (*p_length >= (_DriveLen - 1) &&
_wcsnicmp(*OutTruePath, _Drive, _DriveLen - 1) == 0)
{
name = (*OutTruePath);
if (name[_DriveLen - 1] == L'\\')
drive = File_GetDriveForLetter(name[_DriveLen]);
else
drive = NULL;
if (! drive) {
//
// caller specified invalid path for \sandbox\drive\x
//
*OutTruePath = NULL;
return STATUS_BAD_INITIAL_PC;
}
ULONG len = _DriveLen + 1; /* drive letter */
// skip any suffix after the drive letter
if (File_DriveAddSN) {
WCHAR* ptr = wcschr(*OutTruePath + _DriveLen + 1, L'\\');
if (!ptr) ptr = wcschr(*OutTruePath + _DriveLen + 1, L'\0');
len = (ULONG)(ptr - *OutTruePath);
}
File_GetName_FixTruePrefix(TlsData,
OutTruePath, p_length, len,
drive->path, drive->len);
if (p_convert_links_again) *p_convert_links_again = TRUE;
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
goto check_sandbox_prefix;
}
//
// alternatively, the true path may begin with "\user\all" which,
// is a box convention for the AllUsers home folder. or, with
// "\user\current", which is a box convention for the home folder
// of the current user. both cases must be translated similarly
// to the "\drive\x" case above.
//
// note that we temporarily use the COPY_NAME_BUFFER here, but
// that's ok because it hasn't been initialized yet
//
else if (*p_length >= _UserLen &&
_wcsnicmp(*OutTruePath, _User, _UserLen) == 0) {
if (File_AllUsersLen && *p_length >= _UserAllLen &&
_wcsnicmp(*OutTruePath, _UserAll, _UserAllLen) == 0) {
File_GetName_FixTruePrefix(TlsData,
OutTruePath, p_length, _UserAllLen,
File_AllUsers, File_AllUsersLen);
} else if (File_CurrentUserLen &&
*p_length >= _UserCurrentLen && _wcsnicmp(
*OutTruePath, _UserCurrent, _UserCurrentLen) == 0) {
File_GetName_FixTruePrefix(TlsData,
OutTruePath, p_length, _UserCurrentLen,
File_CurrentUser, File_CurrentUserLen);
} else if (File_PublicUserLen &&
*p_length >= _UserPublicLen && _wcsnicmp(
*OutTruePath, _UserPublic, _UserPublicLen) == 0) {
File_GetName_FixTruePrefix(TlsData,
OutTruePath, p_length, _UserPublicLen,
File_PublicUser, File_PublicUserLen);
} else {
//
// the path is "...\box\user..." but not for user\current or
// user\all, so restore the sandbox prefix and return special
// status for read-only access
//
name = Dll_GetTlsNameBuffer(
TlsData, TRUE_NAME_BUFFER,
(Dll_BoxFilePathLen + *p_length + 1) * sizeof(WCHAR));
wmemmove(name + Dll_BoxFilePathLen, *OutTruePath, *p_length + 1);
wmemcpy(name, Dll_BoxFilePath, Dll_BoxFilePathLen);
*OutTruePath = name;
return STATUS_BAD_INITIAL_PC;
}
if (p_convert_links_again) *p_convert_links_again = TRUE;
}
//
// alternatively, the true path may begin with "\share\..." which,
// is a box convention for remote shares. in this case it has to
// be translated similarly to the "\drive\x" case above.
//
else if (*p_length >= _ShareLen &&
_wcsnicmp(*OutTruePath, _Share, _ShareLen) == 0) {
File_GetName_FixTruePrefix(TlsData,
OutTruePath, p_length, _ShareLen,
File_Mup, File_MupLen);
if (p_convert_links_again) *p_convert_links_again = TRUE;
}
return STATUS_SUCCESS;
}
//---------------------------------------------------------------------------
// File_GetTruePath
//---------------------------------------------------------------------------
_FX NTSTATUS File_GetTruePath(WCHAR *CopyPath, WCHAR **OutTruePath)
{
ULONG length = wcslen(CopyPath);
BOOLEAN is_boxed_path = FALSE;
*OutTruePath = CopyPath;
NTSTATUS status = File_GetTruePathImpl(&length, OutTruePath, NULL, &is_boxed_path, FALSE, NULL, NULL);
if (NT_SUCCESS(status) && !is_boxed_path)
return STATUS_BAD_INITIAL_STACK; // indicate with this error code that the path provided was already the true path
return status;
}
//---------------------------------------------------------------------------
// File_GetName
//---------------------------------------------------------------------------
_FX NTSTATUS File_GetName(
HANDLE RootDirectory, UNICODE_STRING *ObjectName,
WCHAR **OutTruePath, WCHAR **OutCopyPath, ULONG *OutFlags)
{
THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
NTSTATUS status;
ULONG length;
WCHAR *name, *TruePath;
ULONG objname_len;
WCHAR *objname_buf;
const FILE_DRIVE *drive;
BOOLEAN have_trailing_backslash, add_trailing_backslash;
BOOLEAN have_tilde;
BOOLEAN convert_links_again;
BOOLEAN is_boxed_path;
BOOLEAN free_true_path;
#ifdef WOW64_FS_REDIR
BOOLEAN convert_wow64_link = (File_Wow64FileLink) ? TRUE : FALSE;
#else
const BOOLEAN convert_wow64_link = FALSE;
#endif WOW64_FS_REDIR
BOOLEAN no_relocation = FALSE;
WCHAR snapshot_id[FILE_MAX_SNAPSHOT_ID];
snapshot_id[0] = L'\0';
*OutTruePath = NULL;
*OutCopyPath = NULL;
if (OutFlags)
*OutFlags = 0;
if (ObjectName == NO_RELOCATION) {
no_relocation = TRUE;
ObjectName = NULL;
}
if (ObjectName) {
objname_len = ObjectName->Length & ~1;
objname_buf = ObjectName->Buffer;
} else {
objname_len = 0;
objname_buf = NULL;
}
drive = NULL;
free_true_path = FALSE;
//
// if a root handle is specified, we query the full name of the
// root file, and append the ObjectName
//
if (RootDirectory) {
UNICODE_STRING *uni = NULL;
length = 256;
name = Dll_GetTlsNameBuffer(
TlsData, TRUE_NAME_BUFFER, length + objname_len);
status = Obj_GetObjectName(RootDirectory, name, &length);
if (status == STATUS_OBJECT_PATH_INVALID && objname_len == 0) {
//
// special case: if STATUS_OBJECT_PATH_INVALID is returned,
// and the root directory turns out to be a File object,
// and there is no object name, then:
// this is most likely an anonynymous pipe, so return special
// status STATUS_BAD_INITIAL_PC
//
if (Obj_GetObjectType(RootDirectory) == OBJ_TYPE_FILE) {
name[0] = L'\0';
name[1] = L'\0';
*OutTruePath = name;
return STATUS_BAD_INITIAL_PC;
}
}
if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_BUFFER_TOO_SMALL || status == STATUS_INFO_LENGTH_MISMATCH) {
name = Dll_GetTlsNameBuffer(
TlsData, TRUE_NAME_BUFFER, length + objname_len);
status = Obj_GetObjectName(RootDirectory, name, &length);
}
if (! NT_SUCCESS(status))
return status;
uni = &((OBJECT_NAME_INFORMATION *)name)->Name;
#ifdef WOW64_FS_REDIR
//
// if the root directory handle references System32 in a WOW64
// process, for example as a result of opening SysNative, then
// we should not convert that System32 back to SysWow64
//
if (uni->Buffer && convert_wow64_link) {
const ULONG sys32len = File_Wow64FileLink->src_len;
name = uni->Buffer;
length = uni->Length & ~1;
name[length] = L'\0';
if (length >= sys32len
&& _wcsnicmp(name, File_Wow64FileLink->src, sys32len) == 0
&& (name[sys32len] == L'\\' || name[sys32len] == L'\0')) {
convert_wow64_link = FALSE;
}
else {
//
// if the file/directory is located in the sandbox, we still need to check the path
//
ULONG prefixLen = File_FindBoxPrefixLength(name);
if (prefixLen != 0) {
name += prefixLen;
length -= prefixLen;
if (length >= 10 && 0 == Dll_NlsStrCmp(name + 1, File_Snapshot_Prefix, File_Snapshot_PrefixLen)) {
WCHAR* ptr = wcschr(name + 1 + File_Snapshot_PrefixLen, L'\\');
if (ptr) {
length -= (ULONG)(ptr - name);
name = ptr;
}
}
if(length >= File_Wow64System32Len
&& _wcsnicmp(name, File_Wow64System32, File_Wow64System32Len) == 0
&& (name[File_Wow64System32Len] == L'\\' || name[File_Wow64System32Len] == L'\0')) {
convert_wow64_link = FALSE;
}
}
}
}
#endif WOW64_FS_REDIR
if (uni->Buffer) {
*OutTruePath = uni->Buffer;
name = uni->Buffer + uni->Length / sizeof(WCHAR);
} else
*OutTruePath = name;
if (objname_len) {
if (*objname_buf != L':') {
*name = L'\\';
++name;
}
memcpy(name, objname_buf, objname_len);
name += objname_len / sizeof(WCHAR);
}
*name = L'\0';
File_GetName_ConvertLinks(TlsData, OutTruePath, convert_wow64_link);
//
// if no root handle, then we only have the object name to
// work with. it may begin with a DosDevices name "\??\x:"
// which we have to convert to a full path to the device
//
} else if (objname_len) {
if (objname_len >= 6 * sizeof(WCHAR)) {
if (objname_buf[0] == L'\\' && objname_buf[1] == L'?' &&
objname_buf[2] == L'?' && objname_buf[3] == L'\\' &&
objname_buf[5] == L':')
{
drive = File_GetDriveForLetter(objname_buf[4]);
if (! drive)
return STATUS_OBJECT_PATH_NOT_FOUND;
objname_buf += 6;
objname_len -= 6 * sizeof(WCHAR);
}
}
if (drive) {
//
// convert a DosDevices name into a full NT path to
// the device represented by the drive letter
//
name = Dll_GetTlsNameBuffer(
TlsData, TRUE_NAME_BUFFER,
objname_len + (drive->len + 1) * sizeof(WCHAR));
*OutTruePath = name;
wmemcpy(name, drive->path, drive->len);
name += drive->len;
memcpy(name, objname_buf, objname_len);
name += objname_len / sizeof(WCHAR);
*name = L'\0';
} else {
//
// otherwise check if we were already given a full NT path
// to a disk device. if we find a drive here, it also prevents
// the next section of code from trying to translate symlinks
//
drive = File_GetDriveForPath(
objname_buf, objname_len / sizeof(WCHAR));
if (drive) {
name = Dll_GetTlsNameBuffer(
TlsData, TRUE_NAME_BUFFER,
objname_len + sizeof(WCHAR));
*OutTruePath = name;
memcpy(name, objname_buf, objname_len);
name += objname_len / sizeof(WCHAR);
*name = L'\0';
}
}
if (drive) {
File_GetName_ConvertLinks(
TlsData, OutTruePath, convert_wow64_link);
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
} else {
BOOLEAN translated = FALSE;
*OutTruePath = File_GetName_TranslateSymlinks(
TlsData, objname_buf, objname_len, &translated);
if (! *OutTruePath)
return STATUS_OBJECT_PATH_SYNTAX_BAD;
translated |= File_GetName_ConvertLinks(
TlsData, OutTruePath, convert_wow64_link);
if (! translated) {
// remote shares prefixed by \Device\Mup are ok even
// if they were not translated. anything else means
// a device that probably isn't a filesystem, although
// it may also be a volume that is not mounted to a drive
// letter, so check for a reparse point before failing
if (_wcsnicmp(*OutTruePath, File_Mup, File_MupLen) != 0) {
if (! translated)
return STATUS_BAD_INITIAL_PC;
}
}
}
//
// if no root handle, and no object name, then abort
//
} else
return STATUS_OBJECT_PATH_SYNTAX_BAD;
//
// remove duplicate backslashes
//
name = *OutTruePath;
length = wcslen(name);
while (name[0]) {
if (name[0] == L'\\' && name[1] == L'\\') {
ULONG move_len = length - (ULONG)(name - *OutTruePath) + 1;
wmemmove(name, name + 1, move_len);
--length;
} else
++name;
}
//
// remove the trailing backslash. only if the caller is trying to
// open the root directory, we will be putting that backslash
// back onto the true path, before returning
//
name = *OutTruePath;
if (length && name[length - 1] == L'\\') {
--length;
name[length] = L'\0';
have_trailing_backslash = TRUE;
if (OutFlags)
*OutFlags |= FGN_TRAILING_BACKSLASH;
} else
have_trailing_backslash = FALSE;
add_trailing_backslash = FALSE;
//
// make sure the true path begins with the "\device\" prefix.
// note that Windows returns more informative status codes here,
// like STATUS_OBJECT_NAME_NOT_FOUND, STATUS_OBJECT_PATH_NOT_FOUND
// and STATUS_OBJECT_TYPE_MISMATCH. but we take the easy way out
//
if (length < _DeviceLen ||
_wcsnicmp(*OutTruePath, File_Mup, _DeviceLen) != 0) {
return STATUS_OBJECT_PATH_SYNTAX_BAD;
}
//
// if this is a named pipe or mail slot, return special status
//
if ((! drive) && File_IsNamedPipe(*OutTruePath, NULL)) {
return STATUS_BAD_INITIAL_PC;
}
//
// expand short names in the true path, but only if contains a tilde
//
if (wcschr(*OutTruePath, L'~')) {
have_tilde = TRUE;
name = File_GetName_ExpandShortNames(TlsData, *OutTruePath);
length = wcslen(name);
*OutTruePath = name;
} else
have_tilde = FALSE;
//
// if the path leads inside the sandbox, we advance the pointer.
//
convert_links_again = FALSE;
is_boxed_path = FALSE;
TruePath = *OutTruePath; // save pointer in case we need to restore
check_sandbox_prefix:
status = File_GetTruePathImpl(&length, OutTruePath, OutFlags, &is_boxed_path, no_relocation, snapshot_id, &convert_links_again);
if (!NT_SUCCESS(status)) {
if(*OutTruePath == NULL)
*OutTruePath = TruePath;
return status;
}
//
// if we had a tilde, try short name expansion again, now that we
// have translated prefixes like \user\current back into their
// real path names
//
if (have_tilde) {
name = File_GetName_ExpandShortNames(TlsData, *OutTruePath);
*OutTruePath = name;
length = wcslen(name);
}
//
// final conversion of any links (volume reparse points), but only
// in case TruePath was inside the sandbox and we adjusted it
//
if (convert_links_again) {
File_GetName_ConvertLinks(TlsData, OutTruePath, convert_wow64_link);
length = wcslen(*OutTruePath);
}
//
// convert \Windows\SysNative to \Windows\System32
//
#ifdef WOW64_FS_REDIR
if (convert_wow64_link) {
name = *OutTruePath;
length = wcslen(name);
if (length >= File_Wow64SysNativeLen
&& 0 == _wcsnicmp(
name, File_Wow64SysNative, File_Wow64SysNativeLen)
&& (name[File_Wow64SysNativeLen] == L'\\' ||
name[File_Wow64SysNativeLen] == L'\0')
&& (! File_GetName_SkipWow64Link(L""))) {
name = *OutTruePath;
File_GetName_FixTruePrefix(
TlsData, &name, &length,
File_Wow64SysNativeLen,
File_Wow64FileLink->src, File_Wow64FileLink->src_len);
*OutTruePath = name;
}
}
#endif WOW64_FS_REDIR
//
// translate reparse points and use the resulting string
// as the base for creating CopyPath
//
TruePath = File_TranslateTempLinks(*OutTruePath, TRUE);
if (TruePath) {
length = wcslen(TruePath);
//
// if the reparsed path now begins with the sandbox prefix then
// we need to go back
//
if (length >= Dll_BoxFilePathLen &&
0 == Dll_NlsStrCmp(
TruePath, Dll_BoxFilePath, Dll_BoxFilePathLen))
is_boxed_path = TRUE;
else if (File_AltBoxPath && length >= File_AltBoxPathLen &&
0 == Dll_NlsStrCmp(
TruePath, File_AltBoxPath, File_AltBoxPathLen))
is_boxed_path = TRUE;
if (is_boxed_path) {
name = Dll_GetTlsNameBuffer(
TlsData, TRUE_NAME_BUFFER, (length + 1) * sizeof(WCHAR));
wmemcpy(name, TruePath, length + 1);
Dll_Free(TruePath);
TruePath = name;
*OutTruePath = TruePath;
convert_links_again = FALSE;
goto check_sandbox_prefix;
}
//
// otherwise test the reparsed path for open/closed paths and
// then continue to create the copy path
//
free_true_path = TRUE;
if (OutFlags) {
ULONG mp_flags = File_MatchPath(TruePath, OutFlags);
if (PATH_IS_OPEN(mp_flags))
*OutFlags |= FGN_REPARSED_OPEN_PATH;
if (PATH_IS_CLOSED(mp_flags))
*OutFlags |= FGN_REPARSED_CLOSED_PATH;
if (PATH_IS_WRITE(mp_flags))
*OutFlags |= FGN_REPARSED_WRITE_PATH;
}
} else
TruePath = *OutTruePath;
//
// if this is a unboxed path, and we opened it by object,
// check path relocation and update true path accordingly.
//
if (!is_boxed_path && RootDirectory && !no_relocation) {
name = Handle_GetRelocationPath(RootDirectory, objname_len);
if (name) {
*OutTruePath = name;
TruePath = *OutTruePath;
name = (*OutTruePath) + wcslen(*OutTruePath);
if (objname_len) {
*name = L'\\';
++name;
memcpy(name, objname_buf, objname_len);
name += objname_len / sizeof(WCHAR);
}
*name = L'\0';
}
}
//
// now create the copy path, which is the box prefix prepended
// to the true path that we have. note that the copy path will
// still be missing its null terminator.
//
status = File_GetCopyPathImpl(TruePath, OutCopyPath, OutFlags, snapshot_id, have_trailing_backslash, &add_trailing_backslash);
if (!NT_SUCCESS(status))
return status;
if (add_trailing_backslash) {
name = *OutTruePath;
name += wcslen(name);
name[0] = L'\\';
name[1] = L'\0';
}
if (free_true_path)
Dll_Free(TruePath);
//
// debugging helper
//
/*if (IsDebuggerPresent()) {
OutputDebugString(*OutTruePath);
OutputDebugString(L"\n");
}*/
/*if (_wcsicmp(Dll_ImageName, L"acrord32.exe") == 0) {
ULONG len = wcslen(*OutTruePath) + 1;
WCHAR *path = Dll_AllocTemp(len * sizeof(WCHAR));
wmemcpy(path, *OutTruePath, len);
_wcslwr(path);
if (wcsstr(path, L"products.txt")) {
while (! IsDebuggerPresent()) { OutputDebugString(L"BREAK\n"); Sleep(500); }
__debugbreak();
}
}*/
//
// finish
//
return STATUS_SUCCESS;
}
//---------------------------------------------------------------------------
// File_GetName_TranslateSymlinks
//---------------------------------------------------------------------------
_FX WCHAR *File_GetName_TranslateSymlinks(
THREAD_DATA *TlsData, const WCHAR *objname_buf, ULONG objname_len,
BOOLEAN *translated)
{
NTSTATUS status;
HANDLE handle;
OBJECT_ATTRIBUTES objattrs;
UNICODE_STRING objname;
WCHAR *name;
ULONG path_len;
const WCHAR *suffix;
ULONG suffix_len;
// This prevents an additional \device\pipename prefix being added by File_GetBoxedPipeName (thanks Chrome).
if (objname_len > 26 && !_wcsnicmp(objname_buf, L"\\??\\pipe", 8)) {
if (!_wcsnicmp(objname_buf + 8, File_NamedPipe, 17)) {
objname_buf += 8;
}
}
if (objname_len >= 18 && File_IsNamedPipe(objname_buf, NULL)) {
handle = NULL;
goto not_link;
}
InitializeObjectAttributes(
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
objname.Length = (USHORT)objname_len;
objname.Buffer = (WCHAR *)objname_buf;
//
// try to open the longest symbolic link we find. for instance,
// if the object name is \??\PIPE\MyPipe, we will open the link
// "\??\PIPE" even though "\??\" itself is also a link
//
while (1) {
objname.MaximumLength = objname.Length;
status = NtOpenSymbolicLinkObject(
&handle, SYMBOLIC_LINK_QUERY, &objattrs);
if (NT_SUCCESS(status))
break;
if (status == STATUS_ACCESS_DENIED &&
objname.Length <= 1020 * sizeof(WCHAR)) {
//
// if the object is a valid symbolic link but we don't have
// access rights to open the symbolic link then we ask the
// driver to query the link for us
//
WCHAR *path = Dll_AllocTemp(1024 * sizeof(WCHAR));
memcpy(path, objname.Buffer, objname.Length);
path[objname.Length / sizeof(WCHAR)] = L'\0';
status = SbieApi_QuerySymbolicLink(path, 1024 * sizeof(WCHAR));
if (NT_SUCCESS(status)) {
path_len = wcslen(path) * sizeof(WCHAR);
suffix = objname_buf + objname.Length / sizeof(WCHAR);
suffix_len = objname_len - objname.Length;
name = Dll_GetTlsNameBuffer(
TlsData, TRUE_NAME_BUFFER,
path_len + suffix_len + sizeof(WCHAR));
memcpy(name, path, path_len);
Dll_Free(path);
goto copy_suffix;
} else {
Dll_Free(path);
status = STATUS_ACCESS_DENIED;
}
}
//
// path was not a symbolic link, so chop off the
// last path component before checking the path again
//
handle = NULL;
if (objname.Length <= sizeof(WCHAR))
break;
do {
objname.Length -= sizeof(WCHAR);
} while ( objname.Length &&
objname_buf[objname.Length / sizeof(WCHAR)] != L'\\');
if (objname.Length <= sizeof(WCHAR))
break;
}
//
// if we couldn't locate a symbolic link then we're done
//
not_link:
if (! handle) {
name = Dll_GetTlsNameBuffer(
TlsData, TRUE_NAME_BUFFER,
objname_len + sizeof(WCHAR));
memcpy(name, objname_buf, objname_len);
*(name + objname_len / sizeof(WCHAR)) = L'\0';
return name;
}
//
// otherwise query the symbolic link into the true name buffer
//
suffix = objname_buf + objname.Length / sizeof(WCHAR);
suffix_len = objname_len - objname.Length;
memzero(&objname, sizeof(UNICODE_STRING));
path_len = 0;
status = NtQuerySymbolicLinkObject(handle, &objname, &path_len);
if (status != STATUS_BUFFER_TOO_SMALL) {
NtClose(handle);
return NULL;
}
name = Dll_GetTlsNameBuffer(
TlsData, TRUE_NAME_BUFFER,
path_len + suffix_len + sizeof(WCHAR));
objname.Length = (USHORT)(path_len - sizeof(WCHAR));
objname.MaximumLength = (USHORT)path_len;
objname.Buffer = name;
status = NtQuerySymbolicLinkObject(handle, &objname, NULL);
path_len = objname.Length;
NtClose(handle);
//
// the true name buffer contains the expansion of the symbolic link,
// copy the rest of it (the suffix) and invoke ourselves recursively
//
copy_suffix:
if (! NT_SUCCESS(status))
name = NULL;
else {
WCHAR *name2 = name + path_len / sizeof(WCHAR);
memcpy(name2, suffix, suffix_len);
name2 += suffix_len / sizeof(WCHAR);
*name2 = L'\0';
//
// copy the result to the copy name buffer, for the recursive
// invocation of this function. if the recursive invocation
// doesn't find a symbolic link to translate, it will simply
// put the result in the true name buffer, and return
//
name2 = Dll_GetTlsNameBuffer(
TlsData, COPY_NAME_BUFFER,
path_len + suffix_len + sizeof(WCHAR));
memcpy(name2, name, path_len + suffix_len + sizeof(WCHAR));
name = File_GetName_TranslateSymlinks(
TlsData, name2, path_len + suffix_len, translated);
if (name)
*translated = TRUE;
}
return name;
}
//---------------------------------------------------------------------------
// File_GetName_ExpandShortNames2
//---------------------------------------------------------------------------
_FX NTSTATUS File_GetName_ExpandShortNames2(
WCHAR *Path, ULONG index, ULONG backslash_index, PFILE_BOTH_DIRECTORY_INFORMATION info, const ULONG info_size, FILE_SNAPSHOT* Cur_Snapshot)
{
NTSTATUS status;
UNICODE_STRING uni;
OBJECT_ATTRIBUTES ObjAttrs;
HANDLE handle;
IO_STATUS_BLOCK IoStatusBlock;
WCHAR* TmplName;
WCHAR save_char;
save_char = Path[backslash_index + 1];
Path[backslash_index + 1] = L'\0';
TmplName = File_MakeSnapshotPath(Cur_Snapshot, Path);
if(TmplName != NULL)
uni.Buffer = TmplName;
else
uni.Buffer = Path;
uni.Length = wcslen(uni.Buffer) * sizeof(WCHAR);
uni.MaximumLength = uni.Length + sizeof(WCHAR);
InitializeObjectAttributes(
&ObjAttrs, &uni, OBJ_CASE_INSENSITIVE, NULL, NULL);
status = __sys_NtCreateFile(
&handle,
GENERIC_READ | SYNCHRONIZE, // DesiredAccess
&ObjAttrs,
&IoStatusBlock,
NULL, // AllocationSize
0, // FileAttributes
FILE_SHARE_VALID_FLAGS, // ShareAccess
FILE_OPEN, // CreateDisposition
FILE_DIRECTORY_FILE | // CreateOptions
FILE_SYNCHRONOUS_IO_NONALERT,
NULL, // EaBuffer
0); // EaLength
//
// restore original path
//
Path[backslash_index + 1] = save_char;
if (!NT_SUCCESS(status))
return status;
// query long name for short name. if the short name is not
// found with a status of NO_SUCH_FILE, then possibly it was
// already deleted or does not even exist yet. in this case
// we leave the short name as is instead of failing.
save_char = Path[index];
Path[index] = L'\0';
WCHAR ShortName[12 + 1];
if (Cur_Snapshot && Cur_Snapshot->ScramKey && wcslen(&Path[backslash_index + 1]) <= 12)
{
//
// If we are checking in a snapshot we need to unscramble the short name
//
wcscpy(ShortName, &Path[backslash_index + 1]);
File_UnScrambleShortName(ShortName, Cur_Snapshot->ScramKey);
uni.Buffer = ShortName;
}
else
uni.Buffer = &Path[backslash_index + 1];
uni.Length = wcslen(uni.Buffer) * sizeof(WCHAR);
uni.MaximumLength = uni.Length + sizeof(WCHAR);
status = __sys_NtQueryDirectoryFile(
handle,
NULL, NULL, NULL, // Event, ApcRoutine, ApcContext
&IoStatusBlock,
info, info_size, FileBothDirectoryInformation,
TRUE, &uni, FALSE);
NtClose(handle);
Path[index] = save_char; // restore original path
return status;
}
//---------------------------------------------------------------------------
// File_GetName_ExpandShortNames
//---------------------------------------------------------------------------
_FX WCHAR *File_GetName_ExpandShortNames(
THREAD_DATA *TlsData, WCHAR *Path)
{
NTSTATUS status;
PFILE_BOTH_DIRECTORY_INFORMATION info = NULL;
const ULONG info_size = 1024;
ULONG index;
//
// this function scans the input path for any tilde (~) characters
// that may hint to short names, and expands them to the long names.
// it can only translate short names to long names outside the box.
//
info = Dll_AllocTemp(info_size);
status = STATUS_SUCCESS;
for (index = 0; Path[index] != 0; ) {
// scan path string until a tilde (~) is found, but also keep
// the position of the last backslash character before the tilde.
ULONG backslash_index;
ULONG dot_count;
ULONG len;
WCHAR *copy;
for (; Path[index] != L'\0' && Path[index] != L'~'; ++index)
if (Path[index] == L'\\')
backslash_index = index;
if (Path[index] == L'\0') // end of path, no tilde
break;
// a tilde was found, find the first backslash following it,
// and count how many dot (.) characters we see along the way.
// if the tilde is the first character in the component,
// we don't treat the component as a short name
if (index == backslash_index + 1) // begins with a tilde?
dot_count = 99; // probably not a short name
else
dot_count = 0;
for (; Path[index] != L'\0' && Path[index] != L'\\'; ++index)
if (Path[index] == L'.')
++dot_count;
// if more than one dot found, or path component is longer than
// 12 characters (for the 8.3 format), it's not a short name
if (dot_count > 1 || (index - backslash_index - 1) > 12)
continue;
// otherwise open the directory containing the short name component
status = File_GetName_ExpandShortNames2(Path, index, backslash_index, info, info_size, NULL);
if (!NT_SUCCESS(status) && File_Snapshot != NULL)
{
for (FILE_SNAPSHOT* Cur_Snapshot = File_Snapshot; Cur_Snapshot != NULL; Cur_Snapshot = Cur_Snapshot->Parent)
{
status = File_GetName_ExpandShortNames2(Path, index, backslash_index, info, info_size, Cur_Snapshot);
if (NT_SUCCESS(status))
break;
}
}
/*
// stop if we can't open the directory, but file-not-found
// or file-not-a-directory errors may occur because the caller is
// trying to access a directory that exists only in the copy system,
// while we're looking at the true system. so we shouldn't fail.
if (!NT_SUCCESS(status)) {
if (status == STATUS_OBJECT_NAME_NOT_FOUND ||
status == STATUS_OBJECT_PATH_NOT_FOUND ||
status == STATUS_NOT_A_DIRECTORY) {
status = STATUS_SUCCESS;
}
break;
}
if (status == STATUS_NO_SUCH_FILE) { // short name not found,
status = STATUS_SUCCESS; // so don't replace it
continue;
}
if (! NT_SUCCESS(status)) // could not query long name?
break;
*/
if (!NT_SUCCESS(status))
continue;
//
// expand the path with the short name into the copy name buffer,
// then copy it back into the true name buffer
//
len = (wcslen(Path) + 1) * sizeof(WCHAR) + info->FileNameLength;
copy = Dll_GetTlsNameBuffer(TlsData, COPY_NAME_BUFFER, len);
wmemcpy(copy, Path, backslash_index + 1);
len = (backslash_index + 1);
memcpy(copy + len, info->FileName, info->FileNameLength);
len += info->FileNameLength / sizeof(WCHAR);
wcscpy(copy + len, Path + index);
len = (wcslen(copy) + 1) * sizeof(WCHAR);
Path = Dll_GetTlsNameBuffer(TlsData, TRUE_NAME_BUFFER, len);
memcpy(Path, copy, len);
index = backslash_index + info->FileNameLength / sizeof(WCHAR) + 1;
status = STATUS_SUCCESS;
}
// free memory allocated for FILE_BOTH_DIRECTORY_INFORMATION, and return
if (info)
Dll_Free(info);
return Path;
}
//---------------------------------------------------------------------------
// File_GetName_ConvertLinks
//---------------------------------------------------------------------------
_FX BOOLEAN File_GetName_ConvertLinks(
THREAD_DATA *TlsData, WCHAR **OutTruePath, BOOLEAN ConvertWow64Link)
{
WCHAR *name;
ULONG name_len;
FILE_LINK *link;
ULONG retries = 0;
BOOLEAN converted = FALSE;
name = *OutTruePath;
name_len = wcslen(name);
EnterCriticalSection(File_DrivesAndLinks_CritSec);
link = List_Head(File_PermLinks);
while (link) {
const ULONG src_len = link->src_len;
if (name_len >= src_len &&
(name[src_len] == L'\\' || name[src_len] == L'\0') &&
_wcsnicmp(name, link->src, src_len) == 0) {
#ifdef WOW64_FS_REDIR
if (link == File_Wow64FileLink) {
ULONG skip = (! ConvertWow64Link) ? 1
: File_GetName_SkipWow64Link(name + src_len);
if (skip) {
link = List_Next(link);
continue;
}
}
#endif WOW64_FS_REDIR
File_GetName_FixTruePrefix(
TlsData, &name, &name_len,
src_len, link->dst, link->dst_len);
*OutTruePath = name;
converted = TRUE;
link = List_Head(File_PermLinks);
++retries;
if (retries == 16)
break;
} else
link = List_Next(link);
}
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
return converted;
}
//---------------------------------------------------------------------------
// File_GetName_FixTruePrefix
//---------------------------------------------------------------------------
_FX void File_GetName_FixTruePrefix(
THREAD_DATA *TlsData,
WCHAR **OutTruePath, ULONG *InOutLength,
ULONG old_prefix_len,
const WCHAR *new_prefix, ULONG new_prefix_len)
{
//
// input: *OutTruePath points to the TruePath, which is *InOutLength
// characters long, and begins with some prefix that is
// old_prefix_len characters long
//
// process: append the suffix of TruePath to the specifieid new_prefix,
// which is new_prefix_len chacters long
//
// output: *OutTruePath and *InOutLength are adjusted.
//
ULONG suffix_len = *InOutLength - old_prefix_len + /* NULL */ 1;
ULONG len = new_prefix_len + suffix_len;
WCHAR *nm = Dll_GetTlsNameBuffer(
TlsData, COPY_NAME_BUFFER, len * sizeof(WCHAR));
wmemcpy(nm, new_prefix, new_prefix_len);
wmemcpy(nm + new_prefix_len, *OutTruePath + old_prefix_len, suffix_len);
*OutTruePath = Dll_GetTlsNameBuffer(
TlsData, TRUE_NAME_BUFFER, len * sizeof(WCHAR));
wmemcpy(*OutTruePath, nm, len);
*InOutLength = len - /* NULL */ 1;
}
//---------------------------------------------------------------------------
// File_GetName_SkipWow64Link
//---------------------------------------------------------------------------
#ifdef WOW64_FS_REDIR
_FX ULONG File_GetName_SkipWow64Link(const WCHAR *name)
{
typedef (*pfs)(ULONG_PTR x);
ULONG x;
//
// before translating System32 to SysWow64, make sure
// filesystem redirection was not disabled.
//
// note that some programs (e.g. DriverGenius) make the mistake
// of calling Wow64RevertWow64FsRedirection(&x)
// instead of Wow64RevertWow64FsRedirection(x)
// and then our call to the same function sets x to some pointer
// values instead of zero. to counter this, we only consider
// Wow64 redirection disabled if x is exactly 1
//
((pfs)File_Wow64DisableWow64FsRedirection)((ULONG_PTR)&x);
((pfs)File_Wow64RevertWow64FsRedirection)(x);
if (x != 1)
x = 0;
if ((! x) && *name) {
//
// some folders should never be redirected.
//
static const WCHAR *_specialcases[] = {
L"driverstore", // only on Windows 7
L"catroot", L"catroot2",
L"drivers\\etc",
L"logfiles",
L"spool",
NULL
};
ULONG i, n;
++name; // past backslash
for (i = 0; _specialcases[i]; ++i) {
if (i == 0 && Dll_OsBuild < 7600)
continue;
n = wcslen(_specialcases[i]);
if (_wcsnicmp(name, _specialcases[i], n) == 0 &&
(name[n] == L'\\' || name[n] == L'\0')) {
x = 1;
break;
}
}
}
return x;
}
#endif WOW64_FS_REDIR
//---------------------------------------------------------------------------
// File_Wow64FixProcImage
//---------------------------------------------------------------------------
#ifdef WOW64_FS_REDIR
_FX VOID File_Wow64FixProcImage(WCHAR* proc_image_path)
{
if (!proc_image_path)
return;
if (File_Wow64FileLink) {
const ULONG sys32len = File_Wow64FileLink->src_len;
WCHAR* name = File_TranslateDosToNtPath(proc_image_path);
ULONG length = wcslen(name);
if (length >= sys32len
&& _wcsnicmp(name, File_Wow64FileLink->src, sys32len) == 0
&& (name[sys32len] == L'\\' || name[sys32len] == L'\0')) {
wmemcpy(proc_image_path, File_Wow64SysNative, File_Wow64SysNativeLen);
wmemcpy(proc_image_path + File_Wow64SysNativeLen, name + sys32len, length - sys32len + 1);
SbieDll_TranslateNtToDosPath(proc_image_path);
}
Dll_Free(name);
}
}
#endif WOW64_FS_REDIR
//---------------------------------------------------------------------------
// File_GetName_FromFileId
//---------------------------------------------------------------------------
_FX NTSTATUS File_GetName_FromFileId(
OBJECT_ATTRIBUTES *ObjectAttributes,
WCHAR **OutTruePath, WCHAR **OutCopyPath)
{
NTSTATUS status;
HANDLE FileHandle = NULL;
IO_STATUS_BLOCK IoStatusBlock;
OBJECT_ATTRIBUTES objattrs;
UNICODE_STRING objname;
//
// open by FileId requires a parent directory handle
// and an object name with an even length up to 8 bytes
//
if (! ObjectAttributes->ObjectName)
return STATUS_OBJECT_NAME_INVALID;
if (ObjectAttributes->ObjectName->Length > 8)
return STATUS_OBJECT_NAME_INVALID;
if (ObjectAttributes->ObjectName->Length & 1)
return STATUS_OBJECT_NAME_INVALID;
if (! ObjectAttributes->RootDirectory)
return STATUS_OBJECT_PATH_SYNTAX_BAD;
//
// assuming the requested file exists outside the sandbox:
//
// if caller is trying to open by FileId using a parent directory,
// the parent directory may be D:\sandbox\drive\C rather than the
// real C: and this would cause a problem if both the C: and D:
// drives have a file with the same FileId. to workaround this,
// we always prefer to use the real C: as parent directory
//
if (1) {
BOOLEAN IsBoxedPath;
status = SbieDll_GetHandlePath(
ObjectAttributes->RootDirectory, NULL, &IsBoxedPath);
if (IsBoxedPath && (
NT_SUCCESS(status) || (status == STATUS_BAD_INITIAL_PC))) {
WCHAR *path = Dll_AllocTemp(8192);
status = SbieDll_GetHandlePath(
ObjectAttributes->RootDirectory, path, NULL);
if (NT_SUCCESS(status)) {
HANDLE hTrueRoot;
InitializeObjectAttributes(&objattrs,
&objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
RtlInitUnicodeString(&objname, path);
status = __sys_NtCreateFile(
&hTrueRoot, SYNCHRONIZE | FILE_READ_ATTRIBUTES,
&objattrs, &IoStatusBlock, NULL, 0,
FILE_SHARE_VALID_FLAGS,
FILE_OPEN,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
NULL, 0);
if (NT_SUCCESS(status)) {
objattrs.RootDirectory = hTrueRoot;
objattrs.ObjectName = ObjectAttributes->ObjectName;
status = __sys_NtCreateFile(
&FileHandle, SYNCHRONIZE | FILE_READ_ATTRIBUTES,
&objattrs, &IoStatusBlock, NULL, 0,
FILE_SHARE_VALID_FLAGS,
FILE_OPEN,
FILE_OPEN_BY_FILE_ID | FILE_SYNCHRONOUS_IO_NONALERT,
NULL, 0);
if (! NT_SUCCESS(status))
FileHandle = NULL;
NtClose(hTrueRoot);
}
}
Dll_Free(path);
}
}
//
// if we don't have a handle yet, two options are possible:
//
// 1. the specified parent directory was not inside the sandbox,
// i.e. caller specified real C: and not D:\sandbox\drive\C
//
// 2. the file exists in the sandbox but the FileId specified by
// the caller was scrambled/invalid by NtQueryDirectoryFile
// or NtQueryInformationFile
//
// for both cases, try to open the file using the parent directory
// specified by the caller
//
if (! FileHandle) {
status = __sys_NtCreateFile(
&FileHandle, SYNCHRONIZE | FILE_READ_ATTRIBUTES,
ObjectAttributes, &IoStatusBlock, NULL, 0,
FILE_SHARE_VALID_FLAGS, FILE_OPEN,
FILE_OPEN_BY_FILE_ID | FILE_SYNCHRONOUS_IO_NONALERT,
NULL, 0);
if (! NT_SUCCESS(status)) {
//
// if the file exists in the sandbox, and its FileId was
// queried using NtQueryDirectoryFile/NtQueryInformationFile
// then we returned a scrambled FileId, so un-scramble it
//
LARGE_INTEGER FileId;
memzero(&FileId, sizeof(FileId));
memcpy(&FileId, ObjectAttributes->ObjectName->Buffer,
ObjectAttributes->ObjectName->Length);
FileId.LowPart ^= 0xFFFFFFFF;
FileId.HighPart ^= 0xFFFFFFFF;
objname.Length = sizeof(FileId);
objname.MaximumLength = objname.Length;
objname.Buffer = (WCHAR *)&FileId;
InitializeObjectAttributes(&objattrs,
&objname, OBJ_CASE_INSENSITIVE,
ObjectAttributes->RootDirectory, NULL);
status = __sys_NtCreateFile(
&FileHandle, SYNCHRONIZE | FILE_READ_ATTRIBUTES,
&objattrs, &IoStatusBlock, NULL, 0,
FILE_SHARE_VALID_FLAGS, FILE_OPEN,
FILE_OPEN_BY_FILE_ID | FILE_SYNCHRONOUS_IO_NONALERT,
NULL, 0);
if (! NT_SUCCESS(status))
FileHandle = NULL;
}
}
//
// if we got a handle for the FileId, query the real path
//
if (FileHandle) {
status =
File_GetName(FileHandle, NULL, OutTruePath, OutCopyPath, NULL);
NtClose(FileHandle);
} else
status = STATUS_INVALID_PARAMETER;
return status;
}
//---------------------------------------------------------------------------
// File_MatchPath
//---------------------------------------------------------------------------
_FX ULONG File_MatchPath(const WCHAR *path, ULONG *FileFlags)
{
return File_MatchPath2(path, FileFlags, TRUE, TRUE);
}
//---------------------------------------------------------------------------
// File_MatchPath2
//---------------------------------------------------------------------------
_FX ULONG File_MatchPath2(const WCHAR *path, ULONG *FileFlags, BOOLEAN bCheckObjectExists, BOOLEAN bMonitorLog)
{
WCHAR *temp_path;
const WCHAR *ptr;
ULONG PrefixLen;
ULONG mp_flags;
//
// if the path contains a colon that indicates an NTFS
// alternate data stream, create a temporary path without the colon
//
temp_path = NULL;
ptr = wcsrchr(path, L'\\');
if (ptr) {
ptr = wcschr(ptr, L':');
if (ptr) {
ULONG len = (wcslen(path) + 1) * sizeof(WCHAR);
temp_path = Dll_AllocTemp(len);
memcpy(temp_path, path, len);
temp_path[ptr - path] = L'\0';
path = (const WCHAR *)temp_path;
}
}
//
// give read-only access to Sandboxie home folder,
// disregarding any settings that might affect it
//
if (Dll_HomeNtPathLen) {
ULONG path_len = wcslen(path);
if (path_len >= Dll_HomeNtPathLen
&& (path[Dll_HomeNtPathLen] == L'\\' ||
path[Dll_HomeNtPathLen] == L'\0')
&& 0 == Dll_NlsStrCmp(
path, Dll_HomeNtPath, Dll_HomeNtPathLen)) {
mp_flags = PATH_OPEN_FLAG;
goto finish;
}
}
//
// if the File_GetName already ran File_MatchPath on a reparsed
// TruePath then we're done
//
if (FileFlags) {
if ((*FileFlags) & FGN_REPARSED_OPEN_PATH) {
mp_flags = PATH_OPEN_FLAG;
goto finish;
}
if ((*FileFlags) & FGN_REPARSED_CLOSED_PATH) {
mp_flags = PATH_CLOSED_FLAG;
goto finish;
}
if ((*FileFlags) & FGN_REPARSED_WRITE_PATH) {
mp_flags = PATH_WRITE_FLAG;
goto finish;
}
}
//
// check for network paths
//
if (_wcsnicmp(path, File_Redirector, File_RedirectorLen) == 0)
PrefixLen = File_RedirectorLen;
else if (_wcsnicmp(path, File_DfsClientRedir, File_DfsClientRedirLen) == 0)
PrefixLen = File_DfsClientRedirLen;
else if (_wcsnicmp(path, File_HgfsRedir, File_HgfsRedirLen) == 0)
PrefixLen = File_HgfsRedirLen;
else if (_wcsnicmp(path, File_MupRedir, File_MupRedirLen) == 0)
PrefixLen = File_MupRedirLen;
else
PrefixLen = 0;
//
// if we have a path that looks like
// \Device\LanmanRedirector\;Q:000000000000b09f\server\share\f1.txt
// \Device\Mup\;LanmanRedirector\;Q:000000000000b09f\server\share\f1.txt
// then translate to
// \Device\Mup\server\share\f1.txt
// and test again. We do this because the SbieDrv records paths
// in the \Device\Mup format. See SbieDrv::File_TranslateShares.
//
if (PrefixLen) {
ptr = path + PrefixLen;
if (*ptr == L';')
ptr = wcschr(ptr, L'\\');
else
--ptr;
if (ptr && ptr[0] && ptr[1]) {
ULONG len1 = wcslen(ptr + 1);
ULONG len2 = (File_MupLen + len1 + 8) * sizeof(WCHAR);
WCHAR* path2 = Dll_AllocTemp(len2);
wmemcpy(path2, File_Mup, File_MupLen);
wmemcpy(path2 + File_MupLen, ptr + 1, len1 + 1);
mp_flags = SbieDll_MatchPath2(L'f', path2, bCheckObjectExists, bMonitorLog);
Dll_Free(path2);
goto finish;
}
}
//
// match path
//
mp_flags = SbieDll_MatchPath2((FileFlags ? L'f' : L'p'), path, FALSE, TRUE);
if (mp_flags)
goto finish;
//
// if path references a mount point, we see it as the mount location
// \Device\HarddiskVolume1\MOUNT but the driver sees it as the target
// location \Device\HarddiskVolume2, so check for this case
//
if (FileFlags) {
WCHAR *path2 = File_FixPermLinksForMatchPath(path);
if (path2) {
mp_flags = SbieDll_MatchPath2(L'f', path2, bCheckObjectExists, bMonitorLog);
Dll_Free(path2);
if (PATH_IS_WRITE(mp_flags)) {
(*FileFlags) |= FGN_REPARSED_WRITE_PATH;
goto finish;
}
if (PATH_IS_CLOSED(mp_flags)) {
(*FileFlags) |= FGN_REPARSED_CLOSED_PATH;
goto finish;
}
if (PATH_IS_OPEN(mp_flags)) {
(*FileFlags) |= FGN_REPARSED_OPEN_PATH;
goto finish;
}
}
}
//
// finish
//
finish:
if (temp_path)
Dll_Free(temp_path);
return mp_flags;
}
//---------------------------------------------------------------------------
// File_NtOpenFile
//---------------------------------------------------------------------------
_FX NTSTATUS File_NtOpenFile(
HANDLE *FileHandle,
ACCESS_MASK DesiredAccess,
OBJECT_ATTRIBUTES *ObjectAttributes,
IO_STATUS_BLOCK *IoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions)
{
NTSTATUS status = File_NtCreateFileImpl(
FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
NULL, 0, ShareAccess, FILE_OPEN, OpenOptions, NULL, 0);
status = StopTailCallOptimization(status);
return status;
}
//---------------------------------------------------------------------------
// File_NtCreateFile
//---------------------------------------------------------------------------
_FX NTSTATUS File_NtCreateFile(
HANDLE *FileHandle,
ACCESS_MASK DesiredAccess,
OBJECT_ATTRIBUTES *ObjectAttributes,
IO_STATUS_BLOCK *IoStatusBlock,
LARGE_INTEGER *AllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG CreateDisposition,
ULONG CreateOptions,
void *EaBuffer,
ULONG EaLength)
{
NTSTATUS status = File_NtCreateFileImpl(
FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
AllocationSize, FileAttributes, ShareAccess, CreateDisposition,
CreateOptions, EaBuffer, EaLength);
status = StopTailCallOptimization(status);
return status;
}
//---------------------------------------------------------------------------
// File_NtCreateFileImpl
//---------------------------------------------------------------------------
#ifdef WITH_DEBUG_
static P_NtCreateFile __sys_NtCreateFile_ = NULL;
_FX NTSTATUS File_MyCreateFile(
HANDLE* FileHandle,
ACCESS_MASK DesiredAccess,
OBJECT_ATTRIBUTES* ObjectAttributes,
IO_STATUS_BLOCK* IoStatusBlock,
LARGE_INTEGER* AllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG CreateDisposition,
ULONG CreateOptions,
void* EaBuffer,
ULONG EaLength)
{
NTSTATUS status = __sys_NtCreateFile_(
FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
AllocationSize, FileAttributes, ShareAccess, CreateDisposition,
CreateOptions, EaBuffer, EaLength);
if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer
&& wcsstr(ObjectAttributes->ObjectName->Buffer, L"ext-ms-win-branding-winbrand-l1-1-0.dll") != 0)
{
return status;
}
//if (NT_SUCCESS(status)) DbgPrint("%p: %p\r\n", _ReturnAddress(), *FileHandle);
status = StopTailCallOptimization(status);
return status;
}
#endif
_FX NTSTATUS File_NtCreateFileImpl(
HANDLE *FileHandle,
ACCESS_MASK DesiredAccess,
OBJECT_ATTRIBUTES *ObjectAttributes,
IO_STATUS_BLOCK *IoStatusBlock,
LARGE_INTEGER *AllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG CreateDisposition,
ULONG CreateOptions,
void *EaBuffer,
ULONG EaLength)
{
ULONG LastError;
THREAD_DATA *TlsData = Dll_GetTlsData(&LastError);
NTSTATUS status;
WCHAR *TruePath;
WCHAR *CopyPath;
WCHAR *TruePathColon;
WCHAR *CopyPathColon;
UNICODE_STRING objname;
OBJECT_ATTRIBUTES objattrs;
ULONG FileFlags, FileType, mp_flags;
BOOLEAN HaveCopyParent, HaveCopyFile, HaveTrueParent;
BOOLEAN DeleteOnClose, DeleteChildren;
BOOLEAN IsEmptyCopyFile;
BOOLEAN AlreadyReparsed;
UCHAR HaveTrueFile;
BOOLEAN HaveSnapshotParent;
ULONG TruePathFlags;
WCHAR* OriginalPath;
BOOLEAN TrueOpened;
//char *pPtr = NULL;
BOOLEAN SkipOriginalTry;
//if (wcsstr(Dll_ImageName, L"chrome.exe") != 0) {
// *pPtr = 34;
// //while (! IsDebuggerPresent()) { OutputDebugString(L"BREAK\n"); Sleep(500); }
// // __debugbreak();
//}
#ifdef WITH_DEBUG_
if (__sys_NtCreateFile_ == NULL)
{
__sys_NtCreateFile_ = __sys_NtCreateFile;
__sys_NtCreateFile = File_MyCreateFile;
}
#endif
/*if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer
&& _wcsicmp(ObjectAttributes->ObjectName->Buffer, L"\\??\\PhysicalDrive0") == 0)
{
return __sys_NtCreateFile(
FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
AllocationSize, FileAttributes, ShareAccess, CreateDisposition,
CreateOptions, EaBuffer, EaLength);
}*/
/*if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer
&& wcsstr(ObjectAttributes->ObjectName->Buffer, L"socket_") != NULL ) {
while (! IsDebuggerPresent()) { OutputDebugString(L"BREAK\n"); Sleep(500); }
__debugbreak();
}*/
//
// if this is a recursive invocation of NtCreateFile,
// then pass it as-is down the chain
//
if (TlsData->file_NtCreateFile_lock) {
return __sys_NtCreateFile(
FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
AllocationSize, FileAttributes, ShareAccess, CreateDisposition,
CreateOptions, EaBuffer, EaLength);
}
//
// not a recursive invocation, handle the call here
//
TlsData->file_NtCreateFile_lock = TRUE;
Dll_PushTlsNameBuffer(TlsData);
AlreadyReparsed = FALSE;
if (Dll_OsBuild >= 8400 && Dll_ImageType == DLL_IMAGE_TRUSTED_INSTALLER)
DesiredAccess &= ~ACCESS_SYSTEM_SECURITY; // for TiWorker.exe (W8)
// MSIServer without system
extern BOOLEAN Scm_MsiServer_Systemless;
if ((DesiredAccess & ACCESS_SYSTEM_SECURITY) != 0 && Dll_ImageType == DLL_IMAGE_MSI_INSTALLER && Scm_MsiServer_Systemless
&& ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer
&& _wcsicmp(ObjectAttributes->ObjectName->Buffer + (ObjectAttributes->ObjectName->Length / sizeof(WCHAR)) - 4, L".msi") == 0
){
//
// MSIServer when accessing \??\C:\WINDOWS\Installer\???????.msi files will get a PRIVILEGE_NOT_HELD error when requesting ACCESS_SYSTEM_SECURITY
// However, if we broadly clear this flag we will get Warning 1946 Property 'System.AppUserModel.ID' could not be set on *.lnk files
//
DesiredAccess &= ~ACCESS_SYSTEM_SECURITY;
}
OriginalPath = NULL;
TrueOpened = FALSE;
SkipOriginalTry = FALSE;
__try {
IoStatusBlock->Information = FILE_DOES_NOT_EXIST;
IoStatusBlock->Status = 0;
InitializeObjectAttributes(&objattrs,
&objname, OBJECT_ATTRIBUTES_ATTRIBUTES, NULL, Secure_NormalSD);
/*objattrs.SecurityQualityOfService =
ObjectAttributes->SecurityQualityOfService;*/
//
// remove creation options that can't be honored because the
// SbieDrv has removed privileges
//
if (!Dll_CompartmentMode)
CreateOptions &= ~FILE_OPEN_FOR_BACKUP_INTENT;
//
// get the full paths for the true and copy files.
//
if (CreateOptions & FILE_OPEN_BY_FILE_ID) {
status = File_GetName_FromFileId(
ObjectAttributes, &TruePath, &CopyPath);
FileFlags = 0;
CreateOptions &= ~FILE_OPEN_BY_FILE_ID;
} else {
status = File_GetName(
ObjectAttributes->RootDirectory, ObjectAttributes->ObjectName,
&TruePath, &CopyPath, &FileFlags);
//
// this is some sort of device access
//
if (status == STATUS_OBJECT_PATH_SYNTAX_BAD) {
//
// the driver usually blocks this anyways so try only in app mode
//
if (Dll_CompartmentMode){
SbieApi_MonitorPut2(MONITOR_PIPE, TruePath, FALSE);
return __sys_NtCreateFile(
FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
AllocationSize, FileAttributes, ShareAccess, CreateDisposition,
CreateOptions, EaBuffer, EaLength);
} else {
SbieApi_MonitorPut2(MONITOR_PIPE | MONITOR_DENY, TruePath, FALSE);
}
}
}
SkipOriginalTry = (status == STATUS_BAD_INITIAL_PC);
//if ( (wcsstr(TruePath, L"Harddisk0\\DR0") != 0) || wcsstr(TruePath, L"HarddiskVolume3") != 0) {
// while (! IsDebuggerPresent()) { OutputDebugString(L"BREAK\n"); Sleep(500); }
// __debugbreak();
//}
ReparseLoop:
if (! NT_SUCCESS(status)) {
//
// we may get STATUS_BAD_INITIAL_PC if the caller is trying
// to open something that isn't a file object. this could be
// the volume device rather than any file in the device, or a
// named pipe or mail slot devices, or any other device that
// can't be translated to a drive letter
//
if (status == STATUS_BAD_INITIAL_PC) {
WCHAR *pipe_server = NULL;
ULONG pipe_type = File_IsNamedPipe(TruePath, &pipe_server);
if (pipe_type) {
status = File_NtCreateFilePipe(
FileHandle, DesiredAccess, &objattrs,
ObjectAttributes->SecurityDescriptor,
ObjectAttributes->SecurityQualityOfService,
IoStatusBlock, ShareAccess,
CreateDisposition, CreateOptions,
TruePath, pipe_type, pipe_server);
} else {
//
// the path is neither a disk nor a pipe so we generally
// allow access unless it explicitly matches a closed path
//
mp_flags = SbieDll_MatchPath(L'p', TruePath);
if (PATH_IS_CLOSED(mp_flags)) {
status = STATUS_ACCESS_DENIED;
__leave;
}
//
// initially try with the caller-specified desired access,
// which will be allowed if our driver is ignoring this
// particular device
//
status = __sys_NtCreateFile(
FileHandle, DesiredAccess, ObjectAttributes,
IoStatusBlock, AllocationSize, FileAttributes,
ShareAccess, CreateDisposition, CreateOptions,
EaBuffer, EaLength);
if (status == STATUS_ACCESS_DENIED) {
CreateDisposition = FILE_OPEN;
CreateOptions &= ~FILE_DELETE_ON_CLOSE;
DesiredAccess &= ~FILE_DENIED_ACCESS;
//
// If this is an access on a raw disk device, adapt the requested permissions to what the drivers permits
//
if (ObjectAttributes->ObjectName && &ObjectAttributes->ObjectName->Buffer != NULL && ObjectAttributes->ObjectName->Length > (4 * sizeof(WCHAR))
&& wcsncmp(ObjectAttributes->ObjectName->Buffer, L"\\??\\", 4) == 0
&& (DesiredAccess & ~(SYNCHRONIZE | READ_CONTROL | FILE_READ_EA | FILE_READ_ATTRIBUTES)) != 0)
{
if (!SbieApi_QueryConfBool(NULL, L"AllowRawDiskRead", FALSE))
if ((ObjectAttributes->ObjectName->Length == (6 * sizeof(WCHAR)) && ObjectAttributes->ObjectName->Buffer[5] == L':') // \??\C:
|| wcsncmp(&ObjectAttributes->ObjectName->Buffer[4], L"PhysicalDrive", 13) == 0 // \??\PhysicalDrive1
|| wcsncmp(&ObjectAttributes->ObjectName->Buffer[4], L"Volume", 6) == 0) // \??\Volume{2b985816-4b6f-11ea-bd33-48a4725d5bbe}
{
DesiredAccess &= (SYNCHRONIZE | READ_CONTROL | FILE_READ_EA | FILE_READ_ATTRIBUTES);
}
}
status = __sys_NtCreateFile(
FileHandle, DesiredAccess, ObjectAttributes,
IoStatusBlock, AllocationSize, FileAttributes,
ShareAccess, CreateDisposition, CreateOptions,
EaBuffer, EaLength);
}
}
}
__leave;
}
//
// allow SXS module to intercept open requests during CreateProcess
//
if (TlsData->proc_create_process &&
Sxs_FileCallback(TruePath, FileHandle))
__leave;
//
// check if this is an open or closed path
//
mp_flags = File_MatchPath(TruePath, &FileFlags);
if (PATH_IS_CLOSED(mp_flags)) {
status = STATUS_ACCESS_DENIED;
__leave;
}
if (PATH_IS_OPEN(mp_flags)) {
WCHAR *ReparsedPath = NULL;
RtlInitUnicodeString(&objname, TruePath);
objattrs.SecurityDescriptor = ObjectAttributes->SecurityDescriptor;
/*
objattrs.SecurityQualityOfService =
ObjectAttributes->SecurityQualityOfService;
*/
status = __sys_NtCreateFile(
FileHandle, DesiredAccess, &objattrs,
IoStatusBlock, AllocationSize, FileAttributes,
ShareAccess, CreateDisposition, CreateOptions,
EaBuffer, EaLength);
if ((status == STATUS_ACCESS_DENIED || status == STATUS_OBJECT_NAME_NOT_FOUND) &&
(FileFlags & FGN_REPARSED_OPEN_PATH)) {
//
// the request may fail if the path contains a reparse point,
// in this case try again with the absolute path
//
ReparsedPath = File_TranslateTempLinks(TruePath, FALSE);
if (ReparsedPath) {
WCHAR *ReparsedPath2 =
File_FixPermLinksForMatchPath(ReparsedPath);
if (ReparsedPath2) {
Dll_Free(ReparsedPath);
ReparsedPath = ReparsedPath2;
}
} else
ReparsedPath = File_FixPermLinksForMatchPath(TruePath);
if (ReparsedPath) {
RtlInitUnicodeString(&objname, ReparsedPath);
status = __sys_NtCreateFile(
FileHandle, DesiredAccess, &objattrs,
IoStatusBlock, AllocationSize, FileAttributes,
ShareAccess, CreateDisposition, CreateOptions,
EaBuffer, EaLength);
}
}
if (status == STATUS_ACCESS_DENIED &&
DesiredAccess == MAXIMUM_ALLOWED) {
//
// if we can't get maximum access, try read-only access
//
status = __sys_NtCreateFile(
FileHandle, FILE_GENERIC_READ, &objattrs,
IoStatusBlock, AllocationSize, FileAttributes,
ShareAccess, CreateDisposition, CreateOptions,
EaBuffer, EaLength);
}
if (ReparsedPath)
Dll_Free(ReparsedPath);
if (NT_SUCCESS(status)) TrueOpened = TRUE;
//
// if we got STATUS_OBJECT_PATH_NOT_FOUND on an open path, meaning
// that parent directories are missing outside the sandbox, then
// treat the path as a normal path (not open)
//
// (By Curt) This code makes no sense to me at all. If a path is open, then we should be done here.
// An Sbie user pointed out that currently folders in an open path are being created in the sandbox.
//if (status != STATUS_OBJECT_PATH_NOT_FOUND)
__leave;
//else
//mp_flags &= ~PATH_OPEN_FLAG;
}
//
// if Microsoft Outlook 2010 is writing an OICE_ file used to communicate
// with an embedded previewer that is running with a Restricted Token then
// use the Everyone security descriptor
//
if (Dll_ImageType == DLL_IMAGE_OFFICE_OUTLOOK &&
wcsstr(TruePath, L"\\OICE_")) {
objattrs.SecurityDescriptor = Secure_EveryoneSD;
}
//
// otherwise we have to do the work, so first check parameters.
//
status = File_CheckCreateParameters(
DesiredAccess, CreateDisposition, CreateOptions, -1);
if (! NT_SUCCESS(status))
__leave;
//
// if TruePath and CopyPath contain colons that indicate an NTFS
// alternate data stream, we remove these for now
//
TruePathColon = wcsrchr(TruePath, L'\\');
if (TruePathColon) {
TruePathColon = wcschr(TruePathColon, L':');
if (TruePathColon)
*TruePathColon = L'\0';
}
CopyPathColon = wcsrchr(CopyPath, L'\\');
if (CopyPathColon) {
CopyPathColon = wcschr(CopyPathColon, L':');
if (CopyPathColon)
*CopyPathColon = L'\0';
}
//
// abort early if the parent of CopyPath exists but marked deleted
//
if (!File_Delete_v2)
if (File_CheckDeletedParent(CopyPath)) {
status = STATUS_OBJECT_PATH_NOT_FOUND;
__leave;
}
//
// if the caller is trying to open the image file for write access,
// then deny the request. note that at this point we don't make
// any distinction between true and copy paths
//
if (Ldr_ImageTruePath) {
const ULONG DesiredAccess2 = (DesiredAccess & FILE_DENIED_ACCESS)
& ~(FILE_WRITE_ATTRIBUTES | DELETE);
if (DesiredAccess2 || (CreateDisposition != FILE_OPEN &&
CreateDisposition != FILE_OPEN_IF)) {
if (_wcsicmp(TruePath, Ldr_ImageTruePath) == 0) {
status = STATUS_SHARING_VIOLATION;
__leave;
}
}
}
//
// get the type of the file
//
IsEmptyCopyFile = FALSE;
HaveTrueFile = '?';
RtlInitUnicodeString(&objname, CopyPath);
status = File_GetFileType(&objattrs, FALSE, &FileType, &IsEmptyCopyFile);
HaveSnapshotParent = FALSE;
//
// Check true path relocation
//
WCHAR* OldTruePath = File_ResolveTruePath(TruePath, CopyPath, &TruePathFlags);
if (OldTruePath) {
OriginalPath = TruePath;
TruePath = OldTruePath;
}
if (NT_SUCCESS(status)) {
ULONG TrueFileType;
//
// we got the file type of an existing CopyPath file.
// note that a CopyPath that is marked deleted, is still
// considered to exist
//
HaveCopyParent = TRUE;
HaveCopyFile = TRUE;
if (CreateOptions & FILE_DELETE_ON_CLOSE) {
// $Workaround$ - 3rd party fix
if (Dll_DigitalGuardian && (PATH_IS_WRITE(mp_flags) || PATH_IS_CLOSED(mp_flags)))
{
HaveTrueFile = 'N';
status = STATUS_SUCCESS;
}
else
{
//
// special case: for FILE_DELETE_ON_CLOSE handling, we need
// to know if a TrueFile exists, to decide if we are going
// to really delete CopyPath, or just mark it deleted
//
RtlInitUnicodeString(&objname, TruePath);
status = File_GetFileType(&objattrs, FALSE, &TrueFileType, NULL);
if (NT_SUCCESS(status))
HaveTrueFile = 'Y';
else {
HaveTrueFile = 'N';
status = STATUS_SUCCESS;
}
}
}
}
else if (status == STATUS_OBJECT_NAME_NOT_FOUND || status == STATUS_OBJECT_PATH_NOT_FOUND) {
//
// the CopyPath file does not exist, but its parent path may exist
//
HaveCopyFile = FALSE;
if (status == STATUS_OBJECT_NAME_NOT_FOUND)
HaveCopyParent = TRUE;
else
HaveCopyParent = FALSE;
//
// check if the parent folder exists in a snapshot
//
if (! HaveCopyParent) {
WCHAR* ptr1 = wcsrchr(CopyPath, L'\\');
*ptr1 = L'\0';
//WCHAR* ptr2 = wcsrchr(TruePath, L'\\');
//*ptr2 = L'\0';
Dll_PushTlsNameBuffer(TlsData);
WCHAR* TmplName = File_FindSnapshotPath(CopyPath);
if (TmplName != NULL)
HaveSnapshotParent = TRUE;
Dll_PopTlsNameBuffer(TlsData);
//*ptr2 = L'\\';
*ptr1 = L'\\';
}
//
// we need to check if the true path exists
//
RtlInitUnicodeString(&objname, TruePath);
if (PATH_IS_WRITE(mp_flags)) {
BOOLEAN use_rule_specificity = (Dll_ProcessFlags & SBIE_FLAG_RULE_SPECIFICITY) != 0;
if (use_rule_specificity && SbieDll_HasReadableSubPath(L'f', OriginalPath ? OriginalPath : TruePath)){
//
// When using Rule specificity we need to create some dummy directories
//
File_CreateBoxedPath(OriginalPath ? OriginalPath : TruePath);
}
else if (OriginalPath) {
status = File_GetFileType(&objattrs, FALSE, &FileType, NULL);
if (status == STATUS_NOT_A_DIRECTORY)
status = STATUS_ACCESS_DENIED;
}
else {
//
// for a write-only path, the directory must be the
// first (or: highest level) directory which matches
// the write-only setting. note that File_GetFileType
// will need to use SbieApi_OpenFile in this case
//
// if the request is for a path below the highest level,
// we pretend the path does not exist
//
int depth = File_CheckDepthForIsWritePath(TruePath);
if (depth == 0) {
status = File_GetFileType(&objattrs, TRUE, &FileType, NULL);
if (status == STATUS_NOT_A_DIRECTORY)
status = STATUS_ACCESS_DENIED;
} else {
FileType = 0;
if (depth == 1 || HaveCopyParent || HaveSnapshotParent)
status = STATUS_OBJECT_NAME_NOT_FOUND;
else
status = STATUS_OBJECT_PATH_NOT_FOUND;
}
}
} else {
//
// otherwise not write-only, so do normal File_GetFileType
//
status = STATUS_SUCCESS;
FileType = 0;
if (TruePathFlags) {
if (FILE_PARENT_DELETED(TruePathFlags))
status = STATUS_OBJECT_PATH_NOT_FOUND;
else if (FILE_IS_DELETED(TruePathFlags))
status = STATUS_OBJECT_NAME_NOT_FOUND;
}
if (NT_SUCCESS(status)) {
status = File_GetFileType(&objattrs, FALSE, &FileType, NULL);
}
}
//
// If the "true" file is in an snapshot it can be a deleted one,
// check for this and act acrodingly.
//
if (TruePathFlags & FILE_INSNAPSHOT_FLAG) {
if (FileType & TYPE_DELETED) {
status = STATUS_OBJECT_NAME_NOT_FOUND;
}
}
if ((FileType & TYPE_REPARSE_POINT)
&& (CreateOptions & FILE_OPEN_REPARSE_POINT) == 0
&& (! AlreadyReparsed)) {
//
// the directory we are accessing might be a reparse point
//
WCHAR *ReparsedPath = File_TranslateTempLinks(TruePath, FALSE);
AlreadyReparsed = TRUE;
if (ReparsedPath) {
RtlInitUnicodeString(&objname, ReparsedPath);
status = File_GetName(
NULL, &objname, &TruePath, &CopyPath, &FileFlags);
Dll_Free(ReparsedPath);
goto ReparseLoop;
}
}
if (NT_SUCCESS(status) ||
status == STATUS_OBJECT_NAME_NOT_FOUND ||
status == STATUS_OBJECT_PATH_NOT_FOUND)
{
if (CreateOptions & FILE_DELETE_ON_CLOSE) {
if (NT_SUCCESS(status))
HaveTrueFile = 'Y';
else
HaveTrueFile = 'N';
} else if (NT_SUCCESS(status))
HaveTrueFile = 'y';
if (status == STATUS_OBJECT_PATH_NOT_FOUND)
HaveTrueParent = FALSE;
else
HaveTrueParent = TRUE;
//
// when opening a directory that matches write-only path,
// we force the creation of the corresponding directory
// in the copy system, and remove any read-only attributes
// to make sure File_CheckCreateParameters won't fail
//
if (PATH_IS_WRITE(mp_flags) && NT_SUCCESS(status) && !OriginalPath) {
DesiredAccess |= FILE_GENERIC_WRITE;
FileType &= ~(TYPE_READ_ONLY | TYPE_SYSTEM);
}
status = STATUS_SUCCESS;
}
//
// we don't have CopyPath, but if we did find TruePath, and this
// is a read-only operation, then let the system handle it
// (on the TruePath)
//
if (FileType && (CreateDisposition == FILE_OPEN ||
CreateDisposition == FILE_OPEN_IF)) {
//
// exception: executable images are often accessed as a very
// specific set of parameters that includes access to write
// attributes. in this case we remove the write access.
//
if ((DesiredAccess & FILE_DENIED_ACCESS)
== FILE_WRITE_ATTRIBUTES) {
//
// don't apply the exception is when the call is coming
// from File_SetAttributes or File_RenameFile
//
if (TlsData->file_dont_strip_write_access == 0)
DesiredAccess &= ~FILE_WRITE_ATTRIBUTES;
}
//
// exception: if the only write-access flag is DELETE, and
// FILE_DELETE_ON_CLOSE is not requested, then we can drop
// the DELETE flag. (this combination is commonly requested
// by the Win32 DeleteFile API, and it later calls
// NtSetInformationFile on the returned handle.)
//
if (((DesiredAccess & FILE_DENIED_ACCESS) == DELETE) &&
((CreateOptions & FILE_DELETE_ON_CLOSE) == 0)) {
DesiredAccess &= ~DELETE;
}
//
// firefox starting with version 106 opens plugin exe's with GENERIC_WRITE
// to mitigate this issue we strip this flag when we detect that it tries to
// do that with an exe that exists outside the sandbox
//
// $Workaround$ - 3rd party fix
if (Dll_ImageType == DLL_IMAGE_MOZILLA_FIREFOX && (DesiredAccess & GENERIC_WRITE)) {
const WCHAR *dot = wcsrchr(TruePath, L'.');
if (dot && _wcsicmp(dot, L".exe") == 0)
DesiredAccess &= ~GENERIC_WRITE;
}
//
// having processed the exceptions we can decide if we are
// going to work on the copy file, or if we are going to
// let the system work on the true file
//
if ((DesiredAccess & FILE_DENIED_ACCESS) == 0) {
if (TruePathColon)
*TruePathColon = L':';
RtlInitUnicodeString(&objname, TruePath);
//
// reduce the access, and call the system
//
if (CreateDisposition == FILE_OPEN_IF)
CreateDisposition = FILE_OPEN;
status = __sys_NtCreateFile(
FileHandle, DesiredAccess, &objattrs,
IoStatusBlock, AllocationSize, FileAttributes,
ShareAccess, CreateDisposition, CreateOptions,
EaBuffer, EaLength);
if (NT_SUCCESS(status)) TrueOpened = TRUE;
//if (status == STATUS_ACCESS_DENIED)
//{
// while(!IsDebuggerPresent()) Sleep(50); __debugbreak();
//}
// MSIServer without system
if (status == STATUS_ACCESS_DENIED && Dll_ImageType == DLL_IMAGE_MSI_INSTALLER //&& Scm_MsiServer_Systemless
&& ObjectAttributes->ObjectName->Buffer && ObjectAttributes->ObjectName->Length >= 34
&& _wcsicmp(ObjectAttributes->ObjectName->Buffer + (ObjectAttributes->ObjectName->Length / sizeof(WCHAR)) - 11, L"\\Config.Msi") == 0
) {
//
// MSI must not fail accessing \??\C:\WINDOWS\Installer\Config.msi but this folder is readable only for system,
// so we create a boxed copy instead and open it
//
RtlInitUnicodeString(&objname, CopyPath);
status = __sys_NtCreateFile(
FileHandle, DesiredAccess, &objattrs,
IoStatusBlock, AllocationSize, FileAttributes,
ShareAccess, FILE_OPEN_IF, FILE_DIRECTORY_FILE,
EaBuffer, EaLength);
}
//
// special case for SandboxieCrypto on Windows Vista,
// which tries to open catdb that are locked by
// the real CryptSvc process. convert read-only access
// to write access so the files can be migrated
//
// similarly for Windows Update and its DataStore.edb.
//
// similarly for the Windows 8 WebCache dllhost and
// its WebCache v01/v24 data files
//
// otherwise, return results directly to caller
//
if (Dll_ImageType != DLL_IMAGE_SANDBOXIE_CRYPTO &&
Dll_ImageType != DLL_IMAGE_SANDBOXIE_WUAU &&
Dll_ImageType != DLL_IMAGE_DLLHOST_WININET_CACHE)
__leave;
if (status != STATUS_SHARING_VIOLATION)
__leave;
DesiredAccess |= FILE_GENERIC_WRITE;
status = STATUS_SUCCESS;
}
}
}
//
// abort early if the parent of CopyPath exists but marked deleted
//
if (FILE_PATH_DELETED(TruePathFlags)) { // actual file or its parent
if (!HaveCopyFile && (HaveTrueFile == 'Y' || HaveTrueFile == 'y')) { // if this is set status will be success
FileType = 0;
status = STATUS_OBJECT_PATH_NOT_FOUND;
//
// if this is a create operation check if the parent folder is ok and if so clear the error
//
if (CreateDisposition != FILE_OPEN && CreateDisposition != FILE_OVERWRITE) {
if (!FILE_PARENT_DELETED(TruePathFlags)) { // parent not deleted
status = STATUS_SUCCESS;
}
}
}
}
if (! NT_SUCCESS(status))
__leave;
//
// check creation parameters again, now that we know the file type
//
status = File_CheckCreateParameters(
DesiredAccess, CreateDisposition, CreateOptions, FileType);
if (status == STATUS_OBJECT_NAME_NOT_FOUND &&
(! HaveCopyParent) && (! HaveSnapshotParent) && (! HaveTrueParent)) {
//
// special case: File_CheckCreateParameters returns
// "file not found" but both true and copy parent directory
// is missing, so convert error to "path not found"
//
status = STATUS_OBJECT_PATH_NOT_FOUND;
}
if (status == STATUS_FILE_IS_A_DIRECTORY &&
TruePathColon && CopyPathColon &&
(CreateOptions & FILE_NON_DIRECTORY_FILE) &&
(FileType & TYPE_DIRECTORY)) {
//
// special case: accessing an alternate data stream in a
// directory file. File_CheckCreateParameters doesn't know
// about the ADS part, but sees a FILE_NON_DIRECTORY_FILE
// access to a TYPE_DIRECTORY file, and returns error
//
status = STATUS_SUCCESS;
}
if (! NT_SUCCESS(status))
__leave;
//
// if caller wants to delete the file, we have to make sure the
// file (or directory) are deletable (and empty). we don't support
// deletion of alternate data streams
//
if (CreateOptions & FILE_DELETE_ON_CLOSE) {
if (TruePathColon || CopyPathColon) {
status = STATUS_ACCESS_DENIED;
__leave;
}
CreateOptions &= ~FILE_DELETE_ON_CLOSE;
DeleteOnClose = TRUE;
} else
DeleteOnClose = FALSE;
//
// for Windows Explorer, any write access on an existing TruePath
// file that has no corresponding CopyPath file (and disposition
// is FILE_OPEN), is converted to read-only access
// in particular this stops Windows Vista Explorer from creating
// sandboxed files when right-clicking Properties on a file
//
if (Dll_ImageType == DLL_IMAGE_SHELL_EXPLORER &&
(TlsData->file_dont_strip_write_access == 0)) {
if ((! DeleteOnClose) && (! HaveCopyFile) && FileType
&& (CreateDisposition == FILE_OPEN)
&& PATH_NOT_WRITE(mp_flags)) {
RtlInitUnicodeString(&objname, TruePath);
status = __sys_NtCreateFile(
FileHandle, FILE_GENERIC_READ, &objattrs,
IoStatusBlock, AllocationSize, FileAttributes,
ShareAccess, CreateDisposition, CreateOptions,
EaBuffer, EaLength);
if (NT_SUCCESS(status)) TrueOpened = TRUE;
__leave;
}
}
//
// if we got here, we will have to work on the CopyPath, so we
// have to create the parent directories, if they're not there
//
// we don't do this for write-only paths because we are
// pretending there is nothing outside the copy system
//
if (! HaveCopyParent) {
if (!HaveTrueParent && Dll_ImageType == DLL_IMAGE_MSI_INSTALLER && Scm_MsiServer_Systemless
&& wcsstr(CopyPath, L"\\system32\\config\\systemprofile\\") != NULL) {
//
// MSI must not fail accessing \??\C:\WINDOWS\system32\config\systemprofile\AppData\Local\Temp\
// but this folder is readable only for system, so we create a boxed copy instead and open it
//
HaveTrueParent = TRUE;
}
if (HaveTrueParent || HaveSnapshotParent) {
status = File_CreatePath(OriginalPath ? OriginalPath : TruePath, CopyPath);
} else
status = STATUS_OBJECT_PATH_NOT_FOUND;
if (! NT_SUCCESS(status))
__leave;
}
//
// if TruePath exists while CopyPath does not (note that a file marked
// deleted is considered to exist), then:
//
if ((! HaveCopyFile) && FileType) {
BOOLEAN IsWritePath = FALSE;
if ((FileType & TYPE_DIRECTORY) && PATH_IS_WRITE(mp_flags))
IsWritePath = TRUE;
//
// if the operation is to open the existing file non-destructively;
// or if the operation is destructive but only on one of the streams
// within the file; then we migrate TruePath into CopyPath.
//
// (note that at this point in the program flow, a non-destructive
// open, for a CopyPath that does not exist, must also include
// write access, or else it would have been handled earlier already)
//
if (FileType & TYPE_REPARSE_POINT) {
status = File_MigrateJunction(
TruePath, CopyPath, IsWritePath);
} else if (CreateDisposition == FILE_OPEN ||
CreateDisposition == FILE_OPEN_IF ||
TruePathColon) {
BOOLEAN WithContents = TRUE;
if (FileType & TYPE_FILE) {
//
// we don't actually copy the contents, if the file is
// opened for the intent of deleting it, which is when
// both DELETE and FILE_DELETE_ON_CLOSE are specified.
//
if (DeleteOnClose && (
(DesiredAccess & FILE_DENIED_ACCESS) == DELETE)) {
WithContents = FALSE;
} else if (Dll_ImageType == DLL_IMAGE_WINDOWS_MEDIA_PLAYER) {
//
// Windows Media Player CurrentDatabase_xxx.wmdb file
//
WCHAR *dot = wcsrchr(TruePath, L'.');
if (dot && _wcsicmp(dot + 1, L"wmdb") == 0) {
WithContents = FALSE;
}
}
} else {
WithContents = FALSE;
}
status = File_MigrateFile(
TruePath, CopyPath, IsWritePath, WithContents);
//
// if the file is to be overwritten, as opposed to superseded,
// we don't care about contents but we must copy its attributes
// from TruePath
//
} else if ( CreateDisposition == FILE_OVERWRITE ||
CreateDisposition == FILE_OVERWRITE_IF) {
status = File_MigrateFile(
TruePath, CopyPath, IsWritePath, FALSE);
}
//
// if migration reports the file is too big, then ask for
// read-only access to the TruePath
//
if (! NT_SUCCESS(status)) {
if (status == STATUS_BAD_INITIAL_PC) {
if (TruePathColon)
*TruePathColon = L':';
RtlInitUnicodeString(&objname, TruePath);
DesiredAccess &= ~FILE_DENIED_ACCESS;
CreateOptions &= ~FILE_DELETE_ON_CLOSE;
status = __sys_NtCreateFile(
FileHandle, DesiredAccess, &objattrs, IoStatusBlock,
AllocationSize, FileAttributes, ShareAccess,
CreateDisposition, CreateOptions, EaBuffer, EaLength);
if (NT_SUCCESS(status)) TrueOpened = TRUE;
}
__leave;
}
}
//
// an alternative case is if the caller is asking to create CopyPath
// through a destructive operation, and CopyPath is marked deleted.
// in this case we physically delete the stale CopyPath.
//
if (!File_Delete_v2) {
DeleteChildren = FALSE;
if (HaveCopyFile && (FileType & TYPE_DELETED) &&
(CreateDisposition != FILE_OPEN)) {
RtlInitUnicodeString(&objname, CopyPath);
status = __sys_NtDeleteFile(&objattrs);
if (! NT_SUCCESS(status))
__leave;
FileType = 0;
if ((CreateOptions & FILE_DIRECTORY_FILE) && (! CopyPathColon)) {
//
// if the caller is re-creating a directory that was already
// deleted (and marked so) in the sandbox, then we should mark
// everything in it as deleted, after it has been re-created
//
DeleteChildren = TRUE;
}
}
}
//
// Note: This is disabled in the driver since Win 10 1903 (see my comments in file.c in File_Generic_MyParseProc).
// if the caller specifies write attributes, this is only permitted
// on non-directory files, so we must be sure to tell the driver
//
/*if (DesiredAccess & DIRECTORY_JUNCTION_ACCESS) {
if ((CreateOptions & FILE_DIRECTORY_FILE) ||
(FileType & TYPE_DIRECTORY) &&
(! TruePathColon) && (! CopyPathColon)) {
DesiredAccess &= ~DIRECTORY_JUNCTION_ACCESS;
DesiredAccess |= FILE_GENERIC_READ;
} else {
CreateOptions |= FILE_NON_DIRECTORY_FILE;
}
}*/
//
// finally we are ready to execute the caller's request on CopyPath.
//
// if FILE_DELETE_ON_CLOSE was specified on a copy file that has
// no matching true file, then try to really delete the file
//
if (CopyPathColon)
*CopyPathColon = L':';
RtlInitUnicodeString(&objname, CopyPath);
if (DeleteOnClose && (File_Delete_v2 || HaveTrueFile == 'N')) {
CreateOptions |= FILE_DELETE_ON_CLOSE;
DesiredAccess |= DELETE;
}
status = __sys_NtCreateFile(
FileHandle, DesiredAccess | FILE_READ_ATTRIBUTES,
&objattrs, IoStatusBlock, AllocationSize, FileAttributes,
ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength);
if (!NT_SUCCESS(status) && DeleteOnClose && !File_Delete_v2 && HaveTrueFile == 'N') {
CreateOptions &= ~FILE_DELETE_ON_CLOSE;
DesiredAccess &= ~DELETE;
status = __sys_NtCreateFile(
FileHandle, DesiredAccess | FILE_READ_ATTRIBUTES,
&objattrs, IoStatusBlock, AllocationSize, FileAttributes,
ShareAccess, CreateDisposition, CreateOptions,
EaBuffer, EaLength);
}
//
// if the file was opened with FILE_DELETE_ON_CLOSE, then invoke
// File_MarkDeleted to mark it deleted. otherwise make sure
// the creation timestamp is not marked deleted.
//
if (NT_SUCCESS(status)) {
if (DeleteOnClose) {
//
// if we have a corresponding true file, then mark the copy
// file deleted, unless it's an alternate data stream
//
if (HaveTrueFile == 'Y') {
if (CopyPathColon)
status = STATUS_ACCESS_DENIED;
else if (File_Delete_v2)
status = File_MarkDeleted_v2(OriginalPath ? OriginalPath : TruePath);
else
status = File_MarkDeleted(*FileHandle, CopyPath);
}
} else {
BOOLEAN IsRecover = FALSE;
//
// file was not opened for deletion, but NTFS file systems
// may sometimes persist an (out of date) creation time
//
if (!File_Delete_v2)
status = File_SetCreateTime(*FileHandle, CopyPath);
if (NT_SUCCESS(status)) {
if (CreateOptions & FILE_DIRECTORY_FILE) {
//
// if a directory has been created over a deleted
// directory, then mark all its children deleted
//
if (!File_Delete_v2) {
if (DeleteChildren) {
TlsData->file_NtCreateFile_lock = FALSE;
File_MarkChildrenDeleted(TruePath);
}
}
} else {
if (FileType == 0 || IsEmptyCopyFile ||
(FileType & TYPE_DELETED)) {
//
// if a non-directory file has been created where one
// did not exist before (even outside the sandbox),
// or was deleted, then record it for recovery
//
IsRecover = File_RecordRecover(*FileHandle, TruePath);
}
}
//
// if a file or directory has been created where one did
// not exist before (even outside the sandbox), or was
// deleted, then check the short name for duplicates
//
if ((! IsRecover) &&
(FileType == 0 || (FileType & TYPE_DELETED))) {
BOOLEAN adjusted = File_AdjustShortName(
TruePath, CopyPath, *FileHandle);
if (adjusted) {
//
// file handle was closed in File_AdjustShortName
//
status = __sys_NtCreateFile(
FileHandle, DesiredAccess | FILE_READ_ATTRIBUTES,
&objattrs, IoStatusBlock,
AllocationSize, FileAttributes,
ShareAccess, FILE_OPEN, CreateOptions,
EaBuffer, EaLength);
if (! NT_SUCCESS(status))
*FileHandle = NULL;
}
}
}
}
if (! NT_SUCCESS(status)) {
if (*FileHandle) {
NtClose(*FileHandle);
*FileHandle = NULL;
}
IoStatusBlock->Information = 0;
}
}
//
// finish
//
} __except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
//
// we can get access denied in a restricted Chrome sandbox process:
// perhaps we tried to access a file/device in the box which isn't
// accessible to a restricted token, but the real file might be
// accessible, so try to access the real file
//
if (Dll_RestrictedToken && status == STATUS_ACCESS_DENIED && !SkipOriginalTry) {
status = __sys_NtCreateFile(
FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
AllocationSize, FileAttributes, ShareAccess, CreateDisposition,
CreateOptions, EaBuffer, EaLength);
if (NT_SUCCESS(status)) TrueOpened = TRUE; // is that right?
if (! NT_SUCCESS(status))
status = STATUS_ACCESS_DENIED;
}
//
// Relocation, if we opened a relocated location we need to
// store the original true path for the File_GetName function
//
if (TrueOpened && OriginalPath) {
Handle_SetRelocationPath(*FileHandle, OriginalPath);
}
//
// finish
//
Dll_PopTlsNameBuffer(TlsData);
TlsData->file_NtCreateFile_lock = FALSE;
__try {
if (! NT_SUCCESS(status))
IoStatusBlock->Status = status;
} __except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
SetLastError(LastError);
return status;
}
//---------------------------------------------------------------------------
// File_CheckCreateParameters
//---------------------------------------------------------------------------
_FX NTSTATUS File_CheckCreateParameters(
ACCESS_MASK DesiredAccess, ULONG CreateDisposition,
ULONG CreateOptions, ULONG FileType)
{
BOOLEAN FileExists;
BOOLEAN FileReadOnly, FileSystem;
//
// if the caller is asking for synchronous operation, then
// DesiredAccess must include SYNCHRONIZE
//
if ( (CreateOptions & (
FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT)) &&
((DesiredAccess & SYNCHRONIZE) == 0)) {
return STATUS_INVALID_PARAMETER;
}
//
// the caller cannot specify both directory and non-directory
//
if ((CreateOptions &
(FILE_DIRECTORY_FILE | FILE_NON_DIRECTORY_FILE))
== (FILE_DIRECTORY_FILE | FILE_NON_DIRECTORY_FILE)) {
return STATUS_INVALID_PARAMETER;
}
//
// if the caller specifies the directory file bit in CreateOptions,
// then CreateDisposition must not be FILE_SUPERSEDE,
// FILE_OVERWRITE or FILE_OVERWRITE_IF
//
if (CreateOptions & FILE_DIRECTORY_FILE)
{
if (CreateDisposition == FILE_SUPERSEDE ||
CreateDisposition == FILE_OVERWRITE ||
CreateDisposition == FILE_OVERWRITE_IF) {
return STATUS_INVALID_PARAMETER;
}
}
//
// if CreateOptions specifies FILE_DELETE_ON_CLOSE, then
// DesiredAccess must specify DELETE
//
if (CreateOptions & FILE_DELETE_ON_CLOSE &&
(! (DesiredAccess & DELETE))) {
return STATUS_INVALID_PARAMETER;
}
//
// split off the FILE_DELETE_ON_CLOSE bit of FileType into FileExists,
// and reset that bit in FileType, for easier comparisons
//
if (FileType == -1)
return STATUS_SUCCESS;
FileExists = FALSE;
FileReadOnly = FALSE;
FileSystem = FALSE;
if (FileType & TYPE_DELETED) {
FileType = 0;
} else if (FileType != 0) {
FileExists = TRUE;
if (FileType & TYPE_READ_ONLY)
FileReadOnly = TRUE;
if (FileType & TYPE_SYSTEM)
FileSystem = TRUE;
FileType &= TYPE_DIRECTORY | TYPE_FILE;
}
//
// file must already exist (and not be marked as deleted),
// for FILE_OPEN or FILE_OVERWRITE requests
//
if ((! FileExists) && (
CreateDisposition == FILE_OPEN ||
CreateDisposition == FILE_OVERWRITE)) {
return STATUS_OBJECT_NAME_NOT_FOUND;
}
//
// file must not be a directory, for FILE_SUPERSEDE, FILE_OVERWRITE
// and FILE_OVERWRITE_IF requests. the status code differentiates
// between trying to overwrite a directory when FILE_NON_DIRECTORY_FILE
// bit is specified, and when that bit isn't specified.
//
if ((FileType & TYPE_DIRECTORY) && (
CreateDisposition == FILE_SUPERSEDE ||
CreateDisposition == FILE_OVERWRITE ||
CreateDisposition == FILE_OVERWRITE_IF)) {
if (CreateOptions & FILE_NON_DIRECTORY_FILE)
return STATUS_FILE_IS_A_DIRECTORY;
else
return STATUS_OBJECT_NAME_COLLISION;
}
//
// for FILE_CREATE, the file must not exist. if it does exist,
// generally the error code is STATUS_OBJECT_NAME_COLLISION,
// unless explicitly requesting to create a regular file while
// the existing file is a directory, in which case the error
// code is STATUS_FILE_IS_A_DIRECTORY
//
if (FileExists && (CreateDisposition == FILE_CREATE)) {
if ((FileType & TYPE_DIRECTORY) &&
(CreateOptions & FILE_NON_DIRECTORY_FILE))
return STATUS_FILE_IS_A_DIRECTORY;
else
return STATUS_OBJECT_NAME_COLLISION;
}
//
// for FILE_OPEN and FILE_OPEN_IF with an explicit specification
// of a file type, verify that the file type is indeed as expected
//
if (CreateDisposition == FILE_OPEN ||
CreateDisposition == FILE_OPEN_IF) {
if ((CreateOptions & FILE_DIRECTORY_FILE) &&
(FileType & TYPE_FILE))
return STATUS_NOT_A_DIRECTORY;
else if ((CreateOptions & FILE_NON_DIRECTORY_FILE) &&
(FileType & TYPE_DIRECTORY))
return STATUS_FILE_IS_A_DIRECTORY;
}
//
// for most write access requests (explicitly through DesiredAccess,
// or if CreateDisposition is any of the destructive operations), the
// file must not be read-only. access to write attributes and to
// the SACL is allowed even on a read-only file.
//
// additionally, system files (but not hidden files) cannot be the target
// of a destructive operation, regardless of the write access
//
if (FileSystem && (
CreateDisposition == FILE_SUPERSEDE ||
CreateDisposition == FILE_OVERWRITE ||
CreateDisposition == FILE_OVERWRITE_IF)) {
FileReadOnly = TRUE;
}
if (FileReadOnly && (CreateOptions & FILE_DELETE_ON_CLOSE))
return STATUS_CANNOT_DELETE;
if (FileReadOnly && (
CreateDisposition == FILE_OPEN ||
CreateDisposition == FILE_OPEN_IF)) {
//
// if the access is only write attributes or access SACL or DACL
// then pretend the file is not read-only so the access is allowed
//
const ACCESS_MASK DeniedAccess = DesiredAccess & FILE_DENIED_ACCESS;
const ACCESS_MASK AllowedAccess = FILE_WRITE_ATTRIBUTES | DELETE
| ACCESS_SYSTEM_SECURITY
| WRITE_OWNER | WRITE_DAC;
if (DeniedAccess & AllowedAccess)
if ((DeniedAccess & ~(AllowedAccess)) == 0)
FileReadOnly = FALSE;
}
if (FileReadOnly && (
(DesiredAccess & FILE_DENIED_ACCESS) || (
CreateDisposition == FILE_SUPERSEDE ||
CreateDisposition == FILE_OVERWRITE ||
CreateDisposition == FILE_OVERWRITE_IF))) {
if (CreateOptions & FILE_DELETE_ON_CLOSE)
return STATUS_CANNOT_DELETE;
else
return STATUS_ACCESS_DENIED;
}
return STATUS_SUCCESS;
}
//---------------------------------------------------------------------------
// File_GetFileType
//---------------------------------------------------------------------------
_FX NTSTATUS File_GetFileType(
OBJECT_ATTRIBUTES *ObjectAttributes, BOOLEAN IsWritePath,
ULONG *FileType, BOOLEAN *IsEmpty)
{
NTSTATUS status;
FILE_NETWORK_OPEN_INFORMATION info;
ULONG type;
*FileType = 0;
P_NtQueryFullAttributesFile pNtQueryFullAttributesFile = __sys_NtQueryFullAttributesFile;
// special case for File_InitRecoverFolders as its called bfore we hook those functions
if (!pNtQueryFullAttributesFile)
pNtQueryFullAttributesFile = NtQueryFullAttributesFile;
if (IsWritePath) {
status = File_QueryFullAttributesDirectoryFile(
ObjectAttributes->ObjectName->Buffer, &info);
} else {
status = pNtQueryFullAttributesFile(ObjectAttributes, &info);
}
if (! NT_SUCCESS(status)) {
if (status == STATUS_NO_SUCH_FILE)
status = STATUS_OBJECT_NAME_NOT_FOUND;
return status; // with *FileType = 0
}
//
// the type returned is TYPE_DIRECTORY or TYPE_FILE.
// for files marked deleted, we set the TYPE_DELETED bit.
// for read-only files, and hidden/system files, we set other bits.
//
if (info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
type = TYPE_DIRECTORY;
if (info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
type |= TYPE_REPARSE_POINT;
} else {
type = TYPE_FILE;
if (IsEmpty && info.EndOfFile.QuadPart == 0)
*IsEmpty = TRUE;
}
if (info.FileAttributes & FILE_ATTRIBUTE_READONLY)
type |= TYPE_READ_ONLY;
if (info.FileAttributes & FILE_ATTRIBUTE_SYSTEM)
type |= TYPE_SYSTEM;
if (!File_Delete_v2) {
if (IS_DELETE_MARK(&info.CreationTime))
type |= TYPE_DELETED;
}
*FileType = type;
return STATUS_SUCCESS;
}
//---------------------------------------------------------------------------
// File_CheckDeletedParent
//---------------------------------------------------------------------------
_FX BOOLEAN File_CheckDeletedParent(WCHAR *CopyPath)
{
OBJECT_ATTRIBUTES objattrs;
UNICODE_STRING objname;
ULONG FileType;
WCHAR *ptr = NULL;
NTSTATUS status;
//
// remove the last path component so we can open the parent directory
//
while (1) {
WCHAR *ptr_old = ptr;
ptr = wcsrchr(CopyPath, L'\\');
if (ptr_old)
*ptr_old = L'\\';
if ((! ptr) || ptr == CopyPath)
return FALSE;
*ptr = L'\0';
InitializeObjectAttributes(
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
RtlInitUnicodeString(&objname, CopyPath);
if (objname.Length <= Dll_BoxFilePathLen * sizeof(WCHAR)) {
*ptr = L'\\';
return FALSE;
}
status = File_GetFileType(&objattrs, FALSE, &FileType, NULL);
if (status == STATUS_OBJECT_NAME_NOT_FOUND || status == STATUS_OBJECT_PATH_NOT_FOUND)
continue;
if (FileType & TYPE_DELETED) {
*ptr = L'\\';
return TRUE;
}
//
// If we have snapshots check their status, if we have a entry in the most recent snapshot
// than older delete markings are not relevant
//
for (FILE_SNAPSHOT* Cur_Snapshot = File_Snapshot; Cur_Snapshot != NULL; Cur_Snapshot = Cur_Snapshot->Parent)
{
WCHAR* TmplName = File_MakeSnapshotPath(Cur_Snapshot, CopyPath);
if (!TmplName)
break;
RtlInitUnicodeString(&objname, TmplName);
status = File_GetFileType(&objattrs, FALSE, &FileType, NULL);
if (status == STATUS_OBJECT_NAME_NOT_FOUND || status == STATUS_OBJECT_PATH_NOT_FOUND)
continue;
if (FileType & TYPE_DELETED) {
*ptr = L'\\';
return TRUE;
}
if (NT_SUCCESS(status))
break;
}
}
}
//---------------------------------------------------------------------------
// File_CreatePath
//---------------------------------------------------------------------------
_FX NTSTATUS File_CreatePath(WCHAR *TruePath, WCHAR *CopyPath)
{
NTSTATUS status;
OBJECT_ATTRIBUTES objattrs;
UNICODE_STRING objname;
HANDLE handle;
WCHAR *path;
WCHAR *sep, *sep2;
WCHAR savechar, savechar2;
USHORT savelength;
USHORT savemaximumlength;
ULONG TruePath_len, CopyPath_len;
IO_STATUS_BLOCK IoStatusBlock;
FILE_BASIC_INFORMATION basic_info;
BOOLEAN IsDeleted = FALSE;
//
// first we traverse backward along the path, removing the last
// path component each time, and trying to create the path that
// we have left. we stop when we succeed, ie, when we reach
// the end of the existing directory tree.
//
InitializeObjectAttributes(
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, Secure_NormalSD);
RtlInitUnicodeString(&objname, CopyPath);
TruePath_len = wcslen(TruePath);
CopyPath_len = objname.Length / sizeof(WCHAR);
path = objname.Buffer;
sep = path + CopyPath_len;
while (1) {
--sep;
while ((sep > path) && (*sep != L'\\'))
--sep;
if (sep <= path) {
//
// we went back all the way to the first character. this
// shouldn't happen in practice, because we certainly have
// stopped at existing directories before getting here.
//
return STATUS_OBJECT_PATH_INVALID;
}
//
// chop off the last component of the path, and try to open
// or create it. if we succeed, break out of this loop.
// (unless the directory was already there, and marked deleted.)
//
savechar = *sep;
*sep = L'\0';
sep2 = TruePath + TruePath_len - (CopyPath_len - (sep - CopyPath));
savechar2 = *sep2;
*sep2 = L'\0';
savelength = objname.Length;
savemaximumlength = objname.MaximumLength;
objname.Length = (sep - path) * sizeof(WCHAR);
objname.MaximumLength = objname.Length + sizeof(WCHAR);
status = __sys_NtCreateFile(
&handle, FILE_READ_ATTRIBUTES | DELETE, &objattrs,
&IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS,
FILE_OPEN_IF, FILE_DIRECTORY_FILE, NULL, 0);
if (File_Delete_v2) {
if (!NT_SUCCESS(status)) {
IsDeleted = FILE_IS_DELETED(File_IsDeletedEx(TruePath, CopyPath, NULL));
}
}
objname.Length = savelength;
objname.MaximumLength = savemaximumlength;
*sep = savechar;
*sep2 = savechar2;
if (NT_SUCCESS(status)) {
if (!File_Delete_v2) {
status = __sys_NtQueryInformationFile(
handle, &IoStatusBlock, &basic_info,
sizeof(FILE_BASIC_INFORMATION), FileBasicInformation);
if (NT_SUCCESS(status)) {
IsDeleted = IS_DELETE_MARK(&basic_info.CreationTime);
}
}
NtClose(handle);
if (IsDeleted) {
return STATUS_OBJECT_PATH_NOT_FOUND;
}
break;
}
if (status != STATUS_OBJECT_NAME_NOT_FOUND &&
status != STATUS_OBJECT_PATH_NOT_FOUND)
{
return status;
}
}
//
// copy the short file name, for the directory we just created
//
savechar = *sep;
*sep = L'\0';
sep2 = TruePath + TruePath_len - (CopyPath_len - (sep - CopyPath));
savechar2 = *sep2;
*sep2 = L'\0';
if (_wcsicmp(sep2 + 1, sep + 1) == 0)
File_CopyShortName(TruePath, CopyPath);
*sep = savechar;
*sep2 = savechar2;
//
// now traverse forward, creating all the missing directories
// in the hierarchy.
//
while (1) {
++sep;
++sep2;
while (*sep && *sep != L'\\') {
++sep;
++sep2;
}
if (! *sep)
break;
savechar = *sep;
*sep = L'\0';
savelength = objname.Length;
savemaximumlength = objname.MaximumLength;
objname.Length = (sep - path) * sizeof(WCHAR);
objname.MaximumLength = objname.Length + sizeof(WCHAR);
status = __sys_NtCreateFile(
&handle, FILE_READ_ATTRIBUTES | DELETE, &objattrs,
&IoStatusBlock, NULL,
FILE_ATTRIBUTE_NORMAL, FILE_SHARE_VALID_FLAGS,
FILE_OPEN_IF, FILE_DIRECTORY_FILE, NULL, 0);
if (NT_SUCCESS(status)) {
NtClose(handle);
savechar2 = *sep2;
*sep2 = L'\0';
if (_wcsicmp(sep2 + 1, sep + 1) == 0)
File_CopyShortName(TruePath, CopyPath);
*sep2 = savechar2;
}
objname.Length = savelength;
objname.MaximumLength = savemaximumlength;
*sep = savechar;
if (! NT_SUCCESS(status))
break;
}
return status;
}
//---------------------------------------------------------------------------
// File_CopyShortName
//---------------------------------------------------------------------------
_FX NTSTATUS File_CopyShortName(
const WCHAR *TruePath, const WCHAR *CopyPath)
{
NTSTATUS status;
HANDLE handle;
OBJECT_ATTRIBUTES objattrs;
UNICODE_STRING objname;
IO_STATUS_BLOCK IoStatusBlock;
union {
FILE_BOTH_DIRECTORY_INFORMATION dir;
UCHAR space[128];
} info;
WCHAR save_char;
WCHAR *backslash;
//
// open the directory containing the last element of TruePath.
// keep the trailing backslash, in case we are opening the
// root directory of the volume
//
backslash = wcsrchr(TruePath, L'\\');
if ((! backslash) || (! backslash[1]))
return STATUS_SUCCESS;
save_char = backslash[1];
backslash[1] = L'\0';
InitializeObjectAttributes(
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
RtlInitUnicodeString(&objname, TruePath);
status = __sys_NtCreateFile(
&handle, FILE_GENERIC_READ, &objattrs, &IoStatusBlock, NULL,
0, FILE_SHARE_VALID_FLAGS, FILE_OPEN,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
backslash[1] = save_char;
if (! NT_SUCCESS(status))
return status;
//
// query the short name
//
RtlInitUnicodeString(&objname, backslash + 1);
info.dir.NextEntryOffset = tzuk;
status = __sys_NtQueryDirectoryFile(
handle, NULL, NULL, NULL, &IoStatusBlock,
&info.dir, sizeof(info), FileBothDirectoryInformation,
TRUE, &objname, FALSE);
if (status == STATUS_BUFFER_OVERFLOW) {
status = STATUS_SUCCESS;
//
// although STATUS_BUFFER_OVERFLOW should fill as much of the buffer
// as we gave it, some faulty drivers (e.g. avast! pro) may return
// this status without touching the buffer at all, in this case retry
// with a larger buffer
//
if (info.dir.NextEntryOffset == tzuk) {
FILE_BOTH_DIRECTORY_INFORMATION *dir2 = Dll_AllocTemp(1024);
status = __sys_NtQueryDirectoryFile(
handle, NULL, NULL, NULL, &IoStatusBlock,
dir2, 1024, FileBothDirectoryInformation,
TRUE, &objname, FALSE);
memcpy(&info.dir, dir2, sizeof(info));
Dll_Free(dir2);
if (status != STATUS_SUCCESS)
return FALSE;
}
}
NtClose(handle);
//
// set the short file name. if we get STATUS_OBJECT_NAME_COLLISION,
// it means the particular short name is already in use in that
// directory. but we're not going to fail the request over this
//
if (NT_SUCCESS(status) &&
info.dir.ShortNameLength &&
info.dir.ShortNameLength <= 12 * sizeof(WCHAR)) {
FILE_SET_SHORT_NAME_REQ *req;
MSG_HEADER *rpl;
ULONG CopyPath_len = (wcslen(CopyPath) + 1) * sizeof(WCHAR);
ULONG req_len = sizeof(FILE_SET_SHORT_NAME_REQ) + CopyPath_len;
req = (FILE_SET_SHORT_NAME_REQ *)Dll_AllocTemp(req_len);
if (req) {
req->h.length = req_len;
req->h.msgid = MSGID_FILE_SET_SHORT_NAME;
memzero(&req->info, sizeof(req->info));
req->info.FileNameLength = info.dir.ShortNameLength;
memcpy(req->info.FileName, info.dir.ShortName,
req->info.FileNameLength);
req->path_len = CopyPath_len;
wcscpy(req->path, CopyPath);
rpl = SbieDll_CallServer(&req->h);
if (rpl)
Dll_Free(rpl);
Dll_Free(req);
}
}
//
// for a directory file, also copy files times
//
if (NT_SUCCESS(status) &&
info.dir.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
FILE_SET_ATTRIBUTES_REQ *req;
MSG_HEADER *rpl;
ULONG CopyPath_len = (wcslen(CopyPath) + 1) * sizeof(WCHAR);
ULONG req_len = sizeof(FILE_SET_ATTRIBUTES_REQ) + CopyPath_len;
req = (FILE_SET_ATTRIBUTES_REQ *)Dll_AllocTemp(req_len);
if (req) {
req->h.length = req_len;
req->h.msgid = MSGID_FILE_SET_ATTRIBUTES;
memzero(&req->info, sizeof(req->info));
req->info.CreationTime.QuadPart = info.dir.CreationTime.QuadPart;
req->info.LastAccessTime.QuadPart = info.dir.LastAccessTime.QuadPart;
req->info.LastWriteTime.QuadPart = info.dir.LastWriteTime.QuadPart;
req->info.ChangeTime.QuadPart = info.dir.ChangeTime.QuadPart;
req->path_len = CopyPath_len;
wcscpy(req->path, CopyPath);
rpl = SbieDll_CallServer(&req->h);
if (rpl)
Dll_Free(rpl);
Dll_Free(req);
}
}
return status;
}
//---------------------------------------------------------------------------
// File_AdjustShortName
//---------------------------------------------------------------------------
_FX BOOLEAN File_AdjustShortName(
const WCHAR *TruePath, const WCHAR *CopyPath, HANDLE FileHandle)
{
NTSTATUS status;
HANDLE handle;
OBJECT_ATTRIBUTES objattrs;
UNICODE_STRING objname;
IO_STATUS_BLOCK IoStatusBlock;
union {
FILE_BOTH_DIRECTORY_INFORMATION dir;
UCHAR space[128];
} info;
WCHAR save_char;
WCHAR *backslash;
//
// open the directory containing the last element of CopyPath.
// keep the trailing backslash, in case we are opening the
// root directory of the volume
//
backslash = wcsrchr(CopyPath, L'\\');
if ((! backslash) || (! backslash[1]))
return FALSE;
save_char = backslash[1];
backslash[1] = L'\0';
InitializeObjectAttributes(
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
RtlInitUnicodeString(&objname, CopyPath);
status = __sys_NtCreateFile(
&handle, FILE_GENERIC_READ, &objattrs, &IoStatusBlock, NULL,
0, FILE_SHARE_VALID_FLAGS, FILE_OPEN,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (status == STATUS_NOT_A_DIRECTORY) {
status = __sys_NtCreateFile(
&handle, FILE_GENERIC_READ, &objattrs, &IoStatusBlock, NULL,
0, FILE_SHARE_VALID_FLAGS, FILE_OPEN,
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
}
backslash[1] = save_char;
if (! NT_SUCCESS(status))
return FALSE;
//
// query the short name
//
RtlInitUnicodeString(&objname, backslash + 1);
info.dir.NextEntryOffset = tzuk;
status = __sys_NtQueryDirectoryFile(
handle, NULL, NULL, NULL, &IoStatusBlock,
&info.dir, sizeof(info), FileBothDirectoryInformation,
TRUE, &objname, FALSE);
if (status == STATUS_BUFFER_OVERFLOW) {
status = STATUS_SUCCESS;
//
// although STATUS_BUFFER_OVERFLOW should fill as much of the buffer
// as we gave it, some faulty drivers (e.g. avast! pro) may return
// this status without touching the buffer at all, in this case retry
// with a larger buffer
//
if (info.dir.NextEntryOffset == tzuk) {
FILE_BOTH_DIRECTORY_INFORMATION *dir2 = Dll_AllocTemp(1024);
status = __sys_NtQueryDirectoryFile(
handle, NULL, NULL, NULL, &IoStatusBlock,
dir2, 1024, FileBothDirectoryInformation,
TRUE, &objname, FALSE);
memcpy(&info.dir, dir2, sizeof(info));
Dll_Free(dir2);
if (status != STATUS_SUCCESS)
return FALSE;
}
}
NtClose(handle);
if (! NT_SUCCESS(status))
return FALSE;
if (! info.dir.ShortNameLength)
return FALSE;
//
// now remove the last element of TruePath and append the short path
//
backslash = wcsrchr(TruePath, L'\\');
if ((! backslash) || (! backslash[1]))
return STATUS_SUCCESS;
++backslash;
memcpy(backslash, info.dir.ShortName, info.dir.ShortNameLength);
backslash[info.dir.ShortNameLength / sizeof(WCHAR)] = L'\0';
//
// try to open this new combined TruePath
//
RtlInitUnicodeString(&objname, TruePath);
status = __sys_NtCreateFile(
&handle, FILE_GENERIC_READ, &objattrs, &IoStatusBlock, NULL,
0, FILE_SHARE_VALID_FLAGS, FILE_OPEN,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
//
// if we could open a file then it means the newly created copy file
// has the same short name as some true file in the same directory.
// we must generate a random short name instead
//
if (NT_SUCCESS(status)) {
ULONG ticks = GetTickCount();
FILE_SET_SHORT_NAME_REQ *req;
MSG_HEADER *rpl;
ULONG CopyPath_len = (wcslen(CopyPath) + 1) * sizeof(WCHAR);
ULONG req_len = sizeof(FILE_SET_SHORT_NAME_REQ) + CopyPath_len;
NtClose(handle);
NtClose(FileHandle);
req = (FILE_SET_SHORT_NAME_REQ *)Dll_AllocTemp(req_len);
if (req) {
req->h.length = req_len;
req->h.msgid = MSGID_FILE_SET_SHORT_NAME;
memzero(&req->info, sizeof(req->info));
Sbie_snwprintf(req->info.FileName, 12,
L"SB~%05X.%03X", ticks >> 12, ticks & 0xFFF);
req->info.FileNameLength = (8 + 1 + 3) * sizeof(WCHAR);
req->path_len = CopyPath_len;
wcscpy(req->path, CopyPath);
rpl = SbieDll_CallServer(&req->h);
if (rpl)
Dll_Free(rpl);
Dll_Free(req);
}
return TRUE;
}
return FALSE;
}
//---------------------------------------------------------------------------
// File_SetCreateTime
//---------------------------------------------------------------------------
_FX NTSTATUS File_SetCreateTime(HANDLE FileHandle, const WCHAR *CopyPath)
{
NTSTATUS status;
IO_STATUS_BLOCK IoStatusBlock;
FILE_BASIC_INFORMATION info;
status = __sys_NtQueryInformationFile(
FileHandle, &IoStatusBlock, &info,
sizeof(FILE_BASIC_INFORMATION), FileBasicInformation);
if (NT_SUCCESS(status)) {
if (IS_DELETE_MARK(&info.CreationTime)) {
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
info.CreationTime.HighPart = ft.dwHighDateTime;
info.CreationTime.LowPart = ft.dwLowDateTime;
status = File_SetAttributes(FileHandle, CopyPath, &info);
}
}
return status;
}
//---------------------------------------------------------------------------
// File_MarkDeleted
//---------------------------------------------------------------------------
_FX NTSTATUS File_MarkDeleted(HANDLE FileHandle, const WCHAR *CopyPath)
{
NTSTATUS status;
IO_STATUS_BLOCK IoStatusBlock;
FILE_BASIC_INFORMATION info;
//
// get the type of the file
//
status = __sys_NtQueryInformationFile(
FileHandle, &IoStatusBlock,
&info, sizeof(FILE_BASIC_INFORMATION), FileBasicInformation);
if (! NT_SUCCESS(status))
return status;
//
// if we are trying to mark a directory as deleted, it must not
// have any children. we use our NtQueryDirectoryFile to merge
// true/copy directories and hide files already marked deleted.
//
// we pass FilePath rather than FileHandle, because the handle
// may have been opened for asynchronous access.
//
if (info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
status = File_DeleteDirectory(CopyPath, FALSE);
if (status == STATUS_DELETE_PENDING)
status = STATUS_SUCCESS;
if (! NT_SUCCESS(status))
return status;
}
//
// mark the file deleted
//
info.CreationTime.HighPart = DELETE_MARK_HIGH;
info.CreationTime.LowPart = DELETE_MARK_LOW;
status = File_SetAttributes(FileHandle, CopyPath, &info);
if (status == STATUS_DELETE_PENDING)
status = STATUS_SUCCESS;
return status;
}
//---------------------------------------------------------------------------
// File_QueryFullAttributesDirectoryFile
//---------------------------------------------------------------------------
_FX NTSTATUS File_QueryFullAttributesDirectoryFile(
const WCHAR *TruePath, FILE_NETWORK_OPEN_INFORMATION *FileInformation)
{
IO_STATUS_BLOCK MyIoStatusBlock;
HANDLE FileHandle;
NTSTATUS status;
//
// try to use SbieApi_OpenFile which will open the file, bypassing
// ClosedFilePath settings, but only if it a directory file. it
// returns a handle that can only be used to query file attributes
//
status = SbieApi_OpenFile(&FileHandle, TruePath);
if (NT_SUCCESS(status)) {
status = __sys_NtQueryInformationFile(
FileHandle, &MyIoStatusBlock,
FileInformation, sizeof(FILE_BASIC_INFORMATION),
FileBasicInformation);
NtClose(FileHandle);
if (NT_SUCCESS(status)) {
//
// convert FILE_BASIC_INFORMATION
// into FILE_NETWORK_OPEN_INFORMATION
//
const FILE_BASIC_INFORMATION *BasicInfo =
(FILE_BASIC_INFORMATION *)FileInformation;
ULONG FileAttrs = BasicInfo->FileAttributes;
FileInformation->AllocationSize.QuadPart = 0;
FileInformation->EndOfFile.QuadPart = 0;
FileInformation->FileAttributes = FileAttrs;
}
}
return status;
}
//---------------------------------------------------------------------------
// File_CheckDepthForIsWritePath
//---------------------------------------------------------------------------
_FX ULONG File_CheckDepthForIsWritePath(const WCHAR *TruePath)
{
ULONG FileFlags, mp_flags, len;
WCHAR *copy, *ptr;
//
// given a path that matches a write-only setting, this function
// removes the last path component in each iteration to find out
// how deep the input path is relative to the write-only setting
//
len = wcslen(TruePath);
if (! len)
return 0;
copy = Dll_AllocTemp((len + 1) * sizeof(WCHAR));
wmemcpy(copy, TruePath, len + 1);
while (copy[len - 1] == L'\\') {
--len;
copy[len] = L'\0';
}
FileFlags = 0;
len = 0;
while (1) {
ptr = wcsrchr(copy, L'\\');
if (! ptr)
break;
*ptr = L'\0';
mp_flags = File_MatchPath(copy, &FileFlags);
if (PATH_NOT_WRITE(mp_flags))
break;
++len;
}
Dll_Free(copy);
return len;
}
//---------------------------------------------------------------------------
// File_NtQueryAttributesFile
//---------------------------------------------------------------------------
_FX NTSTATUS File_NtQueryAttributesFile(
OBJECT_ATTRIBUTES *ObjectAttributes,
FILE_BASIC_INFORMATION *FileInformation)
{
NTSTATUS status;
FILE_NETWORK_OPEN_INFORMATION info;
//
// NtQueryAttributesFile doesn't always return the correct CreationTime
// timestamp, so use NtQueryFullAttributesFile. it also saves the
// trouble of having to duplicate the logic used in that other function
//
status = File_NtQueryFullAttributesFileImpl(ObjectAttributes, &info);
if (NT_SUCCESS(status)) {
FILE_NETWORK_OPEN_INFORMATION *src = &info;
FILE_BASIC_INFORMATION *dst = FileInformation;
dst->CreationTime.QuadPart = src->CreationTime.QuadPart;
dst->LastAccessTime.QuadPart = src->LastAccessTime.QuadPart;
dst->LastWriteTime.QuadPart = src->LastWriteTime.QuadPart;
dst->ChangeTime.QuadPart = src->ChangeTime.QuadPart;
dst->FileAttributes = src->FileAttributes;
}
status = StopTailCallOptimization(status);
return status;
}
//---------------------------------------------------------------------------
// File_NtQueryFullAttributesFile
//---------------------------------------------------------------------------
_FX NTSTATUS File_NtQueryFullAttributesFile(
OBJECT_ATTRIBUTES *ObjectAttributes,
FILE_NETWORK_OPEN_INFORMATION *FileInformation)
{
NTSTATUS status = File_NtQueryFullAttributesFileImpl(ObjectAttributes, FileInformation);
if (status == STATUS_OBJECT_NAME_NOT_FOUND && Dll_ImageType == DLL_IMAGE_MSI_INSTALLER
&& ObjectAttributes != NULL && ObjectAttributes->ObjectName != NULL
// ObjectAttributes->ObjectName == "\\??\\C:\\Config.Msi" // or any other system drive
&& ObjectAttributes->ObjectName->Buffer && ObjectAttributes->ObjectName->Length == 34
&& _wcsicmp(ObjectAttributes->ObjectName->Buffer + 6, L"\\Config.Msi") == 0
) {
//
// MSI bug: this must not fail, hence we create the directory and retry
//
CreateDirectory(ObjectAttributes->ObjectName->Buffer, NULL);
status = File_NtQueryFullAttributesFileImpl(ObjectAttributes, FileInformation);
}
status = StopTailCallOptimization(status);
return status;
}
//---------------------------------------------------------------------------
// File_NtQueryFullAttributesFileImpl
//---------------------------------------------------------------------------
_FX NTSTATUS File_NtQueryFullAttributesFileImpl(
OBJECT_ATTRIBUTES *ObjectAttributes,
FILE_NETWORK_OPEN_INFORMATION *FileInformation)
{
ULONG LastError;
THREAD_DATA *TlsData = Dll_GetTlsData(&LastError);
NTSTATUS status, status2;
OBJECT_ATTRIBUTES objattrs;
UNICODE_STRING objname;
WCHAR *TruePath;
WCHAR *CopyPath;
ULONG FileFlags, FileAttrs, mp_flags;
ULONG TruePathFlags;
WCHAR* OriginalPath;
//
// special case: when it starts, the Windows Explorer process looks
// for Autorun.inf files on all drives (including removable) and that's
// just too slow
//
if (Dll_ImageType == DLL_IMAGE_SHELL_EXPLORER) {
if (ObjectAttributes && ObjectAttributes->ObjectName &&
ObjectAttributes->ObjectName->Length == 36 &&
ObjectAttributes->ObjectName->Buffer &&
_wcsicmp(ObjectAttributes->ObjectName->Buffer + 7,
L"autorun.inf") == 0) {
return STATUS_OBJECT_NAME_NOT_FOUND;
}
}
//
// otherwise continue with normal processing
//
FileAttrs = -1;
Dll_PushTlsNameBuffer(TlsData);
InitializeObjectAttributes(
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
__try {
//
// get the file name we're trying to open
//
status = File_GetName(
ObjectAttributes->RootDirectory, ObjectAttributes->ObjectName,
&TruePath, &CopyPath, &FileFlags);
if (! NT_SUCCESS(status)) {
if (status == STATUS_BAD_INITIAL_PC) {
//
// if we get STATUS_BAD_INITIAL_PC here, this is most likely
// an attempt to query the attributes of the root directory,
// so we can do it on the true path
//
wcscat(TruePath, L"\\");
RtlInitUnicodeString(&objname, TruePath);
status = __sys_NtQueryFullAttributesFile(
&objattrs, FileInformation);
}
__leave;
}
//
// check if this is a closed path
//
mp_flags = File_MatchPath(TruePath, &FileFlags);
if (PATH_IS_CLOSED(mp_flags)) {
//
// query the attributes of a directory file, bypassing
// ClosedFilePath (see File_QueryFullAttributesDirectoryFile)
//
// for non-directory files, abort with status access denied
//
status = File_QueryFullAttributesDirectoryFile(
TruePath, FileInformation);
if (! NT_SUCCESS(status))
status = STATUS_ACCESS_DENIED;
__leave;
}
//
// check if this is an open path
//
if (PATH_IS_OPEN(mp_flags)) {
RtlInitUnicodeString(&objname, TruePath);
status = __sys_NtQueryFullAttributesFile(&objattrs, FileInformation);
if (NT_SUCCESS(status))
FileAttrs = FileInformation->FileAttributes;
__leave;
}
//
// try NtQueryFullAttributesFile on the CopyPath first
//
if (!File_Delete_v2)
if (File_CheckDeletedParent(CopyPath)) {
status = STATUS_OBJECT_PATH_NOT_FOUND;
__leave;
}
RtlInitUnicodeString(&objname, CopyPath);
status = __sys_NtQueryFullAttributesFile(&objattrs, FileInformation);
if (NT_SUCCESS(status) || (
status != STATUS_OBJECT_NAME_NOT_FOUND &&
status != STATUS_OBJECT_PATH_NOT_FOUND)) {
if (!File_Delete_v2) {
if (NT_SUCCESS(status) &&
IS_DELETE_MARK(&FileInformation->CreationTime))
status = STATUS_OBJECT_NAME_NOT_FOUND;
}
if (NT_SUCCESS(status))
FileAttrs = FileInformation->FileAttributes;
__leave;
}
//
// Check true path relocation
//
OriginalPath = NULL;
WCHAR* OldTruePath = File_ResolveTruePath(TruePath, CopyPath, &TruePathFlags);
if (OldTruePath) {
OriginalPath = TruePath;
TruePath = OldTruePath;
}
//
// check if this is a write-only path. if the path is not
// the highest level match on the write-only setting, we
// pretend the path does not exist; see also NtCreateFile
//
if (PATH_IS_WRITE(mp_flags)) {
BOOLEAN use_rule_specificity = (Dll_ProcessFlags & SBIE_FLAG_RULE_SPECIFICITY) != 0;
if (use_rule_specificity && SbieDll_HasReadableSubPath(L'f', OriginalPath ? OriginalPath : TruePath)){
//
// When using Rule specificity we need to create some dummy directories
//
File_CreateBoxedPath(OriginalPath ? OriginalPath : TruePath);
}
else if (OriginalPath) {
; // try TruePath which points by now to the snapshot location
}
else {
int depth = File_CheckDepthForIsWritePath(TruePath);
if (depth == 0) {
status = File_QueryFullAttributesDirectoryFile(
TruePath, FileInformation);
if (status == STATUS_NOT_A_DIRECTORY)
status = STATUS_OBJECT_NAME_NOT_FOUND;
}
else if (depth == 1)
status = STATUS_OBJECT_NAME_NOT_FOUND;
else {
// if depth > 1 we leave the status from querying
// the copy path, which would be
// - STATUS_OBJECT_NAME_NOT_FOUND if copy parent exists
// - STATUS_OBJECT_PATH_NOT_FOUND if it does not exist
//
}
if (NT_SUCCESS(status))
FileAttrs = FileInformation->FileAttributes;
__leave;
}
}
//
// if we couldn't find CopyPath, or if it's an open path,
// then try on the TruePath
//
RtlInitUnicodeString(&objname, TruePath);
status2 = __sys_NtQueryFullAttributesFile(&objattrs, FileInformation);
if (TruePathFlags && NT_SUCCESS(status2)) {
//
// if we found only the true file check if its listed as deleted
//
if (FILE_PARENT_DELETED(TruePathFlags)) { // parent deleted
status = STATUS_OBJECT_PATH_NOT_FOUND;
__leave;
} else if (FILE_IS_DELETED(TruePathFlags)) { // path deleted
status = STATUS_OBJECT_NAME_NOT_FOUND;
__leave;
}
}
if (status2 != STATUS_OBJECT_PATH_NOT_FOUND) {
status = status2;
if (NT_SUCCESS(status))
FileAttrs = FileInformation->FileAttributes;
}
//
// finish
//
} __except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
if (FileAttrs != -1 // if successful
&& (FileAttrs & FILE_ATTRIBUTE_DIRECTORY) == 0 // and not directory
&& (FileFlags & FGN_TRAILING_BACKSLASH)) { // but trailing b.s
status = STATUS_OBJECT_NAME_INVALID;
}
Dll_PopTlsNameBuffer(TlsData);
SetLastError(LastError);
return status;
}
//---------------------------------------------------------------------------
// File_NtQueryInformationFile
//---------------------------------------------------------------------------
_FX NTSTATUS File_NtQueryInformationFile(
HANDLE FileHandle,
IO_STATUS_BLOCK *IoStatusBlock,
void *FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass)
{
NTSTATUS status;
ULONG LastError;
THREAD_DATA *TlsData;
WCHAR *TruePath;
WCHAR *CopyPath;
ULONG TruePathLen;
FILE_LINK *file_link;
if (FileInformationClass == FileNetworkPhysicalNameInformation) {
// To support DFS
File_GetName(FileHandle, NULL, &TruePath, &CopyPath, NULL);
if (TruePath)
{
NTSTATUS status2;
HANDLE FileHandleTrue = 0;
OBJECT_ATTRIBUTES objattrs;
UNICODE_STRING objname;
IO_STATUS_BLOCK IoStatusBlock2;
InitializeObjectAttributes(
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
RtlInitUnicodeString(&objname, TruePath);
status2 = __sys_NtCreateFile(
&FileHandleTrue, FILE_READ_ATTRIBUTES | SYNCHRONIZE, &objattrs,
&IoStatusBlock2, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN, FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if(NT_SUCCESS(status2))
{
status = __sys_NtQueryInformationFile(
FileHandleTrue, IoStatusBlock, FileInformation,
Length, FileInformationClass);
NtClose(FileHandleTrue);
return status;
}
}
}
//
// we only handle FileNameInformation here
//
else if (FileInformationClass != FileNameInformation) {
LARGE_INTEGER *FileId = NULL;
status = __sys_NtQueryInformationFile(
FileHandle, IoStatusBlock, FileInformation,
Length, FileInformationClass);
//
// if caller queried the FileId, and the file is in the
// sandbox, then scramble the FileId to make it less likely
// that the file can be opened by it without unscrambling
// (see also File_GetName_FromFileId)
//
// the reason for this is the possibly of files on both C:
// and D: drives having the same FileId. the program may
// wish to open use a handle on drive C: to open using the
// FileId by might end up using a sandbox handle like
// D:\sandbox\drive\C which is actually on drive D:. this
// makes it impossible to figure out if the program wants
// the file on C: or the sandboxed file on D:. to make
// this less likely to be a problem, we scrambe the FileId
// for files in the sandbox. see also NtQueryDirectoryFile
// and File_GetFullInformation
//
if (FileInformationClass == FileInternalInformation &&
NT_SUCCESS(status)) {
FileId = &((FILE_INTERNAL_INFORMATION *)FileInformation)
->IndexNumber;
} else if (FileInformationClass == FileAllInformation &&
(NT_SUCCESS(status) || status == STATUS_BUFFER_OVERFLOW)) {
FileId = &(((FILE_ALL_INFORMATION *)FileInformation)->
InternalInformation.IndexNumber);
}
if (FileId && FileId->QuadPart) {
BOOLEAN IsBoxedPath;
NTSTATUS status2 =
SbieDll_GetHandlePath(FileHandle, NULL, &IsBoxedPath);
if (IsBoxedPath && (NT_SUCCESS(status2)
|| (status2 == STATUS_BAD_INITIAL_PC))) {
FileId->LowPart ^= 0xFFFFFFFF;
FileId->HighPart ^= 0xFFFFFFFF;
}
}
return status;
}
//
// validate buffer length
//
if (Length < sizeof(ULONG) * 4)
return STATUS_INFO_LENGTH_MISMATCH;
//
// get the true path for the handle passed, and copy into buffer
//
TlsData = Dll_GetTlsData(&LastError);
Dll_PushTlsNameBuffer(TlsData);
__try {
status = File_GetName(FileHandle, NULL, &TruePath, &CopyPath, NULL);
if (status == STATUS_BAD_INITIAL_PC)
status = STATUS_SUCCESS;
if (! NT_SUCCESS(status))
__leave;
file_link = File_FindPermLinksForMatchPath(TruePath, wcslen(TruePath));
if (file_link) {
//
// File_GetName may translate a path to a volume that is mounted
// without a drive letter, for example \Device\HarddiskVolume2\XXX
// translates to \Device\HarddiskVolume1\MOUNT\XXX, and we want
// to make sure that we return \XXX rather than \MOUNT\XXX
//
TruePath += file_link->dst_len;
TruePathLen = wcslen(TruePath);
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
} else if (_wcsnicmp(TruePath, File_Mup, File_MupLen) == 0) {
//
// the TruePath may refer to a network device
//
TruePath += File_MupLen - 1;
TruePathLen = wcslen(TruePath);
} else {
//
// secondary check for a network drive
//
WCHAR *NetworkPath = NULL;
ULONG dummy_len;
const FILE_DRIVE *file_drive =
File_GetDriveForUncPath(TruePath, wcslen(TruePath), &dummy_len);
if (file_drive) {
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
NetworkPath = wcschr(TruePath + 8, L'\\');
if (NetworkPath) {
TruePathLen = wcslen(NetworkPath);
wmemmove(TruePath, NetworkPath, TruePathLen + 1);
}
}
if (! NetworkPath) {
//
// otherwise we do normal drive letter processing
//
SbieDll_TranslateNtToDosPath(TruePath);
TruePathLen = wcslen(TruePath);
if (TruePathLen >= 2 && TruePath[1] == L':') {
if (TruePathLen == 2)
TruePathLen = 0;
else {
TruePath += 2;
TruePathLen -= 2;
}
}
}
}
if (TruePathLen == 0) {
TruePath[0] = L'\\';
TruePath[1] = L'\0';
TruePathLen = 1;
}
TruePathLen *= sizeof(WCHAR);
*(ULONG *)FileInformation = TruePathLen;
Length -= sizeof(ULONG);
if (Length > TruePathLen) {
Length = TruePathLen;
status = STATUS_SUCCESS;
} else
status = STATUS_BUFFER_OVERFLOW;
memcpy((ULONG *)FileInformation + 1, TruePath, Length);
IoStatusBlock->Status = status;
IoStatusBlock->Information = sizeof(ULONG) + Length;
//
// finish
//
} __except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
Dll_PopTlsNameBuffer(TlsData);
SetLastError(LastError);
return status;
}
//---------------------------------------------------------------------------
// File_GetFinalPathNameByHandleW
//---------------------------------------------------------------------------
_FX ULONG File_GetFinalPathNameByHandleW(
HANDLE hFile, WCHAR *lpszFilePath, ULONG cchFilePath, ULONG dwFlags)
{
NTSTATUS status;
ULONG rc;
ULONG err;
WCHAR *path, *result;
BOOLEAN IsBoxedPath;
status = SbieDll_GetHandlePath(hFile, NULL, &IsBoxedPath);
if (IsBoxedPath &&
(NT_SUCCESS(status) || (status == STATUS_BAD_INITIAL_PC))) {
//
// the specified file is inside the sandbox, so handle the request
//
path = Dll_AllocTemp(8192);
status = SbieDll_GetHandlePath(hFile, path, NULL);
if (! NT_SUCCESS(status)) {
rc = 0;
err = ERROR_PATH_NOT_FOUND;
} else {
result = File_GetFinalPathNameByHandleW_2(path, dwFlags);
if (! result) {
rc = 0;
err = GetLastError();
} else {
ULONG len = wcslen(result);
if (len >= cchFilePath) {
rc = len + 1;
err = ERROR_NOT_ENOUGH_MEMORY;
} else {
wmemcpy(lpszFilePath, result, len + 1);
rc = len;
err = 0;
}
if (result != path)
Dll_Free(result);
}
}
Dll_Free(path);
}
//
// the file is outside the sandbox, so the system can handle it
//
if (! IsBoxedPath) {
rc = __sys_GetFinalPathNameByHandleW(
hFile, lpszFilePath, cchFilePath, dwFlags);
err = GetLastError();
}
SetLastError(err);
return rc;
}
//---------------------------------------------------------------------------
// File_GetFinalPathNameByHandleW_2
//---------------------------------------------------------------------------
_FX WCHAR *File_GetFinalPathNameByHandleW_2(WCHAR *TruePath, ULONG dwFlags)
{
static const WCHAR *_DosPrefix = L"\\\\?\\UNC\\";
const FILE_DRIVE *file_drive;
const FILE_LINK *file_link;
const WCHAR *suffix;
WCHAR *path;
WCHAR *ReparsedPath;
ULONG TruePath_len;
ULONG suffix_len;
WCHAR drive_letter;
BOOLEAN AddBackslash;
//
// validate input flags
//
dwFlags &= VOLUME_NAME_GUID | VOLUME_NAME_NT | VOLUME_NAME_NONE;
if (dwFlags & VOLUME_NAME_GUID) {
if (dwFlags & (VOLUME_NAME_NT | VOLUME_NAME_NONE)) {
SetLastError(ERROR_INVALID_PARAMETER);
return NULL;
}
} else if (dwFlags & VOLUME_NAME_NT) {
if (dwFlags & (VOLUME_NAME_GUID | VOLUME_NAME_NONE)) {
SetLastError(ERROR_INVALID_PARAMETER);
return NULL;
}
}
TruePath_len = wcslen(TruePath);
//
// handle a network path
//
if (_wcsnicmp(TruePath, File_Mup, File_MupLen) == 0) {
if (dwFlags & VOLUME_NAME_GUID) {
//
// VOLUME_NAME_GUID not supported for network shares
//
SetLastError(ERROR_PATH_NOT_FOUND);
return NULL;
}
AddBackslash = FALSE;
path = wcschr(TruePath + File_MupLen, L'\\');
if (path) {
path = wcschr(path + 1, L'\\');
if (! path) {
// if the path has just one backslash after the
// \device\mup\ prefix then it is the root of the share,
// for example \device\mup\server\share
AddBackslash = TRUE;
}
}
if (dwFlags & VOLUME_NAME_NT) {
path = Dll_AllocTemp((TruePath_len + 4) * sizeof(WCHAR));
wmemcpy(path, TruePath, TruePath_len + 1);
path[1] = L'D'; // \Device\Mup rather than \device\mup
path[8] = L'M';
} else {
suffix = TruePath + File_MupLen - 1;
suffix_len = wcslen(suffix);
path = Dll_AllocTemp((suffix_len + 12) * sizeof(WCHAR));
if (dwFlags & VOLUME_NAME_NONE)
wmemcpy(path, suffix, suffix_len + 1);
else {
wmemcpy(path, _DosPrefix, 8);
wmemcpy(path + 8, suffix, suffix_len + 1);
}
}
if (AddBackslash)
wcscat(path, _DosPrefix + 7);
return path;
}
//
// handle the GUID case
//
if (dwFlags & VOLUME_NAME_GUID) {
return File_GetFinalPathNameByHandleW_3(TruePath, TruePath_len);
}
//
// analyse the path
//
ReparsedPath = NULL;
AddBackslash = FALSE;
drive_letter = 0;
file_link = File_FindPermLinksForMatchPath(TruePath, TruePath_len);
if (file_link) {
//
// if the volume is mounted on a directory then the TruePath here
// will specify the NT path to the location of the mount, i.e.
// \Device\HarddiskVolume1\MOUNT\XXX instead of
// \Device\HarddiskVolume2\XXX
//
// for non-DOS return values we need to convert the path back
// to the form \Device\HarddiskVolume2\XXX. for DOS return
// values we use the drive letter of the mounted location
//
if (file_link->dst_len == TruePath_len)
AddBackslash = TRUE;
if (dwFlags != VOLUME_NAME_DOS) {
//
// for VOLUME_NAME_NT and VOLUME_NAME_NONE
// we want the real device name even if it was mounted
// on a directory and doesn't have a drive letter
//
ReparsedPath = File_FixPermLinksForMatchPath(TruePath);
if (ReparsedPath) {
TruePath = ReparsedPath;
TruePath_len = wcslen(TruePath);
suffix = TruePath + file_link->src_len;
} else {
// release lock by File_FindPermLinksForMatchPath
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
SetLastError(ERROR_PATH_NOT_FOUND);
return NULL;
}
} else {
//
// for VOLUME_NAME_DOS we want a path with a drive letter
//
file_drive = File_GetDriveForPath(TruePath, TruePath_len);
if (! file_drive) {
// release lock by File_FindPermLinksForMatchPath
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
SetLastError(ERROR_PATH_NOT_FOUND);
return NULL;
}
drive_letter = file_drive->letter;
suffix = TruePath + file_drive->len;
// release lock by File_GetDriveForPath
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
}
// release lock by File_FindPermLinksForMatchPath
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
} else {
//
// in the normal case, the volume is mounted on a drive letter
//
file_drive = File_GetDriveForPath(TruePath, TruePath_len);
if (! file_drive) {
SetLastError(ERROR_PATH_NOT_FOUND);
return NULL;
}
if (file_drive->len == TruePath_len)
AddBackslash = TRUE;
drive_letter = file_drive->letter;
suffix = TruePath + file_drive->len;
// release lock by File_GetDriveForPath
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
}
//
// build return path
//
if (dwFlags & VOLUME_NAME_NT) {
path = Dll_AllocTemp((TruePath_len + 4) * sizeof(WCHAR));
wmemcpy(path, TruePath, TruePath_len + 1);
} else if (dwFlags & VOLUME_NAME_NONE) {
suffix_len = wcslen(suffix);
path = Dll_AllocTemp((suffix_len + 4) * sizeof(WCHAR));
wmemcpy(path, suffix, suffix_len + 1);
} else { // VOLUME_NAME_DOS
suffix_len = wcslen(suffix);
path = Dll_AllocTemp((suffix_len + 16) * sizeof(WCHAR));
wmemcpy(path, _DosPrefix, 4);
path[4] = drive_letter;
path[5] = L':';
wmemcpy(path + 6, suffix, suffix_len + 1);
}
if (AddBackslash)
wcscat(path, _DosPrefix + 7);
if (ReparsedPath)
Dll_Free(ReparsedPath);
return path;
}
//---------------------------------------------------------------------------
// File_GetFinalPathNameByHandleW_3
//---------------------------------------------------------------------------
_FX WCHAR *File_GetFinalPathNameByHandleW_3(
WCHAR *TruePath, ULONG TruePath_len)
{
const FILE_DRIVE *file_drive;
const FILE_LINK *file_link;
WCHAR *path;
OBJECT_ATTRIBUTES objattrs;
UNICODE_STRING objname;
IO_STATUS_BLOCK IoStatusBlock;
HANDLE hFile;
NTSTATUS status;
file_link = File_FindPermLinksForMatchPath(TruePath, TruePath_len);
if (file_link) {
path = Dll_AllocTemp((file_link->src_len + 4) * sizeof(WCHAR));
wmemcpy(path, file_link->src, file_link->src_len + 1);
TruePath += file_link->dst_len + 1;
// release lock by File_FindPermLinksForMatchPath
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
} else {
file_drive = File_GetDriveForPath(TruePath, TruePath_len);
if (! file_drive) {
SetLastError(ERROR_PATH_NOT_FOUND);
return NULL;
}
path = Dll_AllocTemp((file_drive->len + 4) * sizeof(WCHAR));
wmemcpy(path, file_drive->path, file_drive->len + 1);
TruePath += file_drive->len + 1;
// release lock by File_GetDriveForPath
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
}
//
// open the root directory on the device and use the real
// GetFinalPathNameByHandleW to get the \\?\Volume{...} path
// and finally append any remaining suffix
//
InitializeObjectAttributes(
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
wcscat(path, L"\\");
RtlInitUnicodeString(&objname, path);
status = __sys_NtCreateFile(
&hFile, FILE_READ_ATTRIBUTES | SYNCHRONIZE,
&objattrs, &IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS,
FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
Dll_Free(path);
if (! NT_SUCCESS(status)) {
SetLastError(ERROR_PATH_NOT_FOUND);
return NULL;
}
path = Dll_AllocTemp((MAX_PATH + TruePath_len + 8) * sizeof(WCHAR));
status = __sys_GetFinalPathNameByHandleW(
hFile, path, MAX_PATH, 1 /* VOLUME_NAME_GUID */);
NtClose(hFile);
if (status == 0 || status > MAX_PATH - 1) {
status = GetLastError();
Dll_Free(path);
SetLastError(status);
return NULL;
}
wcscat(path, TruePath);
return path;
}
//---------------------------------------------------------------------------
// File_NtQueryObjectName
//---------------------------------------------------------------------------
_FX ULONG File_NtQueryObjectName(UNICODE_STRING *ObjectName, ULONG MaxLen)
{
//
// adjust the path returned by NtQueryObject(ObjectNameInformation)
// to not include a sandbox prefix
//
// this is important because the Google Chrome broker uses NtQueryObject
// to confirm the opened handle matches the requested path
//
ULONG Len = ObjectName->Length;
WCHAR *Buf = ObjectName->Buffer;
if (Len >= Dll_BoxFilePathLen * sizeof(WCHAR) &&
0 == Dll_NlsStrCmp(Buf, Dll_BoxFilePath, Dll_BoxFilePathLen)) {
ULONG NewPathLen;
WCHAR *NewPath;
NewPath = File_GetTruePathForBoxedPath(Buf, FALSE);
if (! NewPath) {
// this can happen when ObjectName is exactly
// the sandbox prefix, e.g. X:\SANDBOX\USER\BOXNAME
return 0;
}
NewPathLen = (wcslen(NewPath) + 1) * sizeof(WCHAR);
if (MaxLen < sizeof(UNICODE_STRING) + NewPathLen)
return NewPathLen;
memcpy(Buf, NewPath, NewPathLen);
ObjectName->Length = (USHORT)(wcslen(Buf) * sizeof(WCHAR));
ObjectName->MaximumLength = ObjectName->Length + sizeof(WCHAR);
return ObjectName->MaximumLength;
}
return 0;
}
//---------------------------------------------------------------------------
// File_NtSetInformationFile
//---------------------------------------------------------------------------
_FX NTSTATUS File_NtSetInformationFile(
HANDLE FileHandle,
IO_STATUS_BLOCK *IoStatusBlock,
void *FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass)
{
ULONG LastError = GetLastError();
NTSTATUS status;
BOOLEAN FillIoStatusBlock;
FillIoStatusBlock = TRUE;
//
// set attributes request
//
if (FileInformationClass == FileBasicInformation) {
if (Length < sizeof(FILE_BASIC_INFORMATION))
status = STATUS_INFO_LENGTH_MISMATCH;
else
status = File_SetAttributes(FileHandle, NULL, FileInformation);
//
// delete request
//
} else if (FileInformationClass == FileDispositionInformation ||
FileInformationClass == FileDispositionInformationEx) {
if (Length < sizeof(FILE_DISPOSITION_INFORMATION))
status = STATUS_INFO_LENGTH_MISMATCH;
else
status = File_SetDisposition(
FileHandle, IoStatusBlock, FileInformation, Length, FileInformationClass);
//
// rename request
//
} else if ( FileInformationClass == FileRenameInformation ||
FileInformationClass == FileRenameInformationEx ) {
status = File_RenameFile(FileHandle, FileInformation);
//
// pipe state request on a proxy pipe
//
} else if (
( FileInformationClass == FilePipeInformation ||
FileInformationClass == FileCompletionInformation ||
FileInformationClass == FileIoCompletionNotificationInformation)
&& ((ULONG_PTR)FileHandle & PROXY_PIPE_MASK) == PROXY_PIPE_MASK) {
FillIoStatusBlock = FALSE;
status = File_SetProxyPipe(
FileHandle, IoStatusBlock,
FileInformation, Length, FileInformationClass);
//
// any other request
//
} else {
FillIoStatusBlock = FALSE;
status = __sys_NtSetInformationFile(
FileHandle, IoStatusBlock,
FileInformation, Length, FileInformationClass);
if ((FileInformationClass == FileLinkInformation ||
FileInformationClass == FileHardLinkFullIdInformation)
&& (! NT_SUCCESS(status))) {
//
// we don't support hard links in the sandbox, but return
// STATUS_INVALID_DEVICE_REQUEST and hopefully the caller will
// invoke CopyFile instead. dfsvc.exe (ClickOnce) does that.
//
status = STATUS_INVALID_DEVICE_REQUEST;
FillIoStatusBlock = TRUE;
}
}
if (FillIoStatusBlock) {
__try {
IoStatusBlock->Status = status;
IoStatusBlock->Information = 0;
} __except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
}
SetLastError(LastError);
return status;
}
//---------------------------------------------------------------------------
// File_SetAttributes
//---------------------------------------------------------------------------
_FX NTSTATUS File_SetAttributes(
HANDLE FileHandle, const WCHAR *CopyPath,
FILE_BASIC_INFORMATION *Information)
{
THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
NTSTATUS status;
IO_STATUS_BLOCK IoStatusBlock;
//
// first call the system to handle the request
//
status = __sys_NtSetInformationFile(
FileHandle, &IoStatusBlock,
Information, sizeof(FILE_BASIC_INFORMATION), FileBasicInformation);
if (status != STATUS_ACCESS_DENIED)
return status;
//
// special optimization exception: if caller asked to update
// FILE_BASIC_INFORMATION and provided only the LastAccessTime
// field, we ignore the request, as there is little point to
// migrate a file into the sandbox merely to change its last
// access time. (most likely through Win32 SetFileTime)
//
if (Information->CreationTime.QuadPart == 0 &&
Information->LastAccessTime.QuadPart != 0 &&
Information->LastWriteTime.QuadPart == 0 &&
Information->ChangeTime.QuadPart == 0 &&
Information->FileAttributes == 0)
{
return STATUS_SUCCESS;
}
//
// special optimization exception: if caller asked to update
// FILE_BASIC_INFORMATION and provided -1 for all time fields
// and zero for FileAttributes, then we do nothing
// (this is used by Windows Explorer for some reason)
//
if (Information->CreationTime.QuadPart == -1 &&
Information->LastAccessTime.QuadPart == -1 &&
Information->LastWriteTime.QuadPart == -1 &&
Information->ChangeTime.QuadPart == -1 &&
Information->FileAttributes == 0)
{
return STATUS_SUCCESS;
}
//
// handle the request
//
Dll_PushTlsNameBuffer(TlsData);
__try {
//
// if we weren't given a CopyPath, then we are called from
// NtSetInformationFile and may not actually have a copy file to
// work with -- if NtCreateFile stripped write attributes.
// in this case, we have to migrate the file.
//
if (! CopyPath) {
WCHAR *TruePath;
ULONG FileFlags;
OBJECT_ATTRIBUTES objattrs;
UNICODE_STRING objname;
//
// get the path of the file for which attributes have to be set
//
RtlInitUnicodeString(&objname, L"");
status = File_GetName(
FileHandle, &objname, &TruePath, (WCHAR **)&CopyPath, &FileFlags);
if (! NT_SUCCESS(status))
__leave;
if (FileFlags & FGN_IS_BOXED_PATH)
goto has_copy_path;
//
// migrate the file into the sandbox. because access mask of
// just FILE_WRITE_ATTRIBUTES | SYNCHRONIZE gets the exception
// for executable images (see NtCreateFile), we set a special flag
//
InitializeObjectAttributes(
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
RtlInitUnicodeString(&objname, TruePath);
++TlsData->file_dont_strip_write_access;
status = NtCreateFile(
&FileHandle,
FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
&objattrs, &IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS,
FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
--TlsData->file_dont_strip_write_access;
if (! NT_SUCCESS(status))
__leave;
//
// now try to set the attributes here. we may still get
// STATUS_ACCESS_DENIED if the file is really a directory,
// in this case we fall through to use the FileServer proxy.
// in this case we also query again the full path, to account
// for any reparse points which may have been referenced
//
status = __sys_NtSetInformationFile(
FileHandle, &IoStatusBlock,
Information, sizeof(FILE_BASIC_INFORMATION),
FileBasicInformation);
if (status == STATUS_ACCESS_DENIED) {
NTSTATUS status2;
WCHAR *CopyPath2;
RtlInitUnicodeString(&objname, L"");
status2 = File_GetName(
FileHandle, &objname, &TruePath, &CopyPath2, NULL);
if (NT_SUCCESS(status2))
*((WCHAR **)&CopyPath) = CopyPath2;
}
NtClose(FileHandle);
if (status != STATUS_ACCESS_DENIED)
__leave;
}
has_copy_path:
//
// ask the FileServer proxy in SbieSvc to do the job. this is needed
// because the driver always blocks FILE_WRITE_ATTRIBUTE access on
// directory files, even in the sandbox, to block junction points
//
if (CopyPath) {
MSG_HEADER *rpl;
FILE_SET_ATTRIBUTES_REQ *req;
ULONG CopyPath_len = (wcslen(CopyPath) + 1) * sizeof(WCHAR);
ULONG req_len = sizeof(FILE_SET_ATTRIBUTES_REQ) + CopyPath_len;
req = (FILE_SET_ATTRIBUTES_REQ *)Dll_AllocTemp(req_len);
if (req) {
req->h.length = req_len;
req->h.msgid = MSGID_FILE_SET_ATTRIBUTES;
memcpy(&req->info, Information, sizeof(FILE_BASIC_INFORMATION));
req->path_len = CopyPath_len;
wcscpy(req->path, CopyPath);
rpl = SbieDll_CallServer(&req->h);
if (rpl) {
status = rpl->status;
Dll_Free(rpl);
} else
status = STATUS_ACCESS_DENIED;
}
Dll_Free(req);
}
//
// finish
//
} __except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
Dll_PopTlsNameBuffer(TlsData);
return status;
}
//---------------------------------------------------------------------------
// File_SetDisposition
//---------------------------------------------------------------------------
_FX NTSTATUS File_SetDisposition(
HANDLE FileHandle, IO_STATUS_BLOCK *IoStatusBlock,
void *FileInformation, ULONG Length, FILE_INFORMATION_CLASS FileInformationClass)
{
ULONG LastError;
THREAD_DATA *TlsData = Dll_GetTlsData(&LastError);
UNICODE_STRING uni;
//WCHAR *DosPath;
NTSTATUS status;
ULONG FileFlags;
ULONG mp_flags;
FILE_ATTRIBUTE_TAG_INFORMATION taginfo;
//
// check if the specified path is an open or closed path
//
RtlInitUnicodeString(&uni, L"");
mp_flags = 0;
//DosPath = NULL;
Dll_PushTlsNameBuffer(TlsData);
__try {
WCHAR *TruePath, *CopyPath;
status = File_GetName(
FileHandle, &uni, &TruePath, &CopyPath, &FileFlags);
if (NT_SUCCESS(status)) {
mp_flags = File_MatchPath(TruePath, &FileFlags);
if (PATH_IS_CLOSED(mp_flags))
status = STATUS_ACCESS_DENIED;
else if (PATH_NOT_OPEN(mp_flags)) {
status = __sys_NtQueryInformationFile(
FileHandle, IoStatusBlock,
&taginfo, sizeof(taginfo), FileAttributeTagInformation);
if (NT_SUCCESS(status) && (taginfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0)
__leave;
status = File_DeleteDirectory(CopyPath, TRUE);
if (status != STATUS_DIRECTORY_NOT_EMPTY)
status = STATUS_SUCCESS;
/*
if (NT_SUCCESS(status) && Dll_ChromeSandbox) {
//
// if this is a Chrome sandbox process, we have
// to pass a DOS path to NtDeleteFile rather
// than a file handle
//
ULONG len = wcslen(TruePath);
DosPath = Dll_AllocTemp((len + 8) * sizeof(WCHAR));
wmemcpy(DosPath, TruePath, len + 1);
if (SbieDll_TranslateNtToDosPath(DosPath)) {
len = wcslen(DosPath);
wmemmove(DosPath + 4, DosPath, len + 1);
wmemcpy(DosPath, File_BQQB, 4);
} else {
Dll_Free(DosPath);
DosPath = NULL;
}
}
*/
}
}
} __except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
Dll_PopTlsNameBuffer(TlsData);
//
// handle the request appropriately
//
if (PATH_IS_OPEN(mp_flags) /*|| ((FileFlags & FGN_IS_BOXED_PATH) != 0)*/) { // boxed path fails for directories
status = __sys_NtSetInformationFile(
FileHandle, IoStatusBlock,
FileInformation, Length, FileInformationClass);
} else if (NT_SUCCESS(status)) {
BOOLEAN DeleteOnClose = FALSE;
if (FileInformationClass == FileDispositionInformation) {
DeleteOnClose = ((FILE_DISPOSITION_INFORMATION*)FileInformation)->DeleteFileOnClose;
} else if (FileInformationClass == FileDispositionInformationEx) { // Win 10 RS1 and later
ULONG Flags = ((FILE_DISPOSITION_INFORMATION_EX*)FileInformation)->Flags;
if ((Flags & FILE_DISPOSITION_DELETE) != 0)
DeleteOnClose = TRUE;
else if((Flags & FILE_DISPOSITION_ON_CLOSE) != 0) // FILE_DISPOSITION_ON_CLOSE with no FILE_DISPOSITION_DELETE means clear flag
DeleteOnClose = FALSE;
}
OBJECT_ATTRIBUTES objattrs;
InitializeObjectAttributes(
&objattrs, &uni, OBJ_CASE_INSENSITIVE, FileHandle, NULL);
//
// check if the call to File_NtDeleteFileImpl from the delete handler is expected to fail
// and return the appropriate error
//
FILE_NETWORK_OPEN_INFORMATION info;
if (NT_SUCCESS(__sys_NtQueryFullAttributesFile(&objattrs, &info)) && ((info.FileAttributes & FILE_ATTRIBUTE_READONLY) != 0)) {
status = STATUS_CANNOT_DELETE;
} else {
Handle_SetDeleteOnClose(FileHandle, DeleteOnClose);
}
/*
if (DosPath) {
objattrs.RootDirectory = NULL;
RtlInitUnicodeString(&uni, DosPath);
}
status = File_NtDeleteFileImpl(&objattrs);
*/
IoStatusBlock->Status = 0;
IoStatusBlock->Information = 8;
}
//if (DosPath)
// Dll_Free(DosPath);
SetLastError(LastError);
return status;
}
//---------------------------------------------------------------------------
// File_NtDeleteFile
//---------------------------------------------------------------------------
_FX NTSTATUS File_NtDeleteFile(OBJECT_ATTRIBUTES *ObjectAttributes)
{
NTSTATUS status = File_NtDeleteFileImpl(ObjectAttributes);
status = StopTailCallOptimization(status);
return status;
}
//---------------------------------------------------------------------------
// File_NtDeleteFileImpl
//---------------------------------------------------------------------------
_FX NTSTATUS File_NtDeleteFileImpl(OBJECT_ATTRIBUTES *ObjectAttributes)
{
NTSTATUS status;
HANDLE handle;
IO_STATUS_BLOCK IoStatusBlock;
status = File_NtCreateFileImpl(
&handle, DELETE, ObjectAttributes, &IoStatusBlock, NULL, 0,
FILE_SHARE_VALID_FLAGS, FILE_OPEN, FILE_DELETE_ON_CLOSE | FILE_OPEN_REPARSE_POINT, NULL, 0);
if (NT_SUCCESS(status))
NtClose(handle);
return status;
}
//---------------------------------------------------------------------------
// File_RenameOpenFile
//---------------------------------------------------------------------------
_FX LONG File_RenameOpenFile(
HANDLE file_handle,
const WCHAR* user_dir, const WCHAR* user_name,
BOOLEAN replace_if_exists)
{
//
// in compartment mode we don't need driver assistance we can do things ourselves
// this code is a port of the same routine in the driver
//
NTSTATUS status;
ULONG user_dir_len = (wcslen(user_dir) + 1) * sizeof(WCHAR);
ULONG user_name_len = (wcslen(user_name) + 1) * sizeof(WCHAR);
WCHAR *path, *name;
FILE_RENAME_INFORMATION *info;
ULONG path_len, name_len, info_len;
WCHAR save_char;
HANDLE dir_handle;
OBJECT_ATTRIBUTES objattrs;
UNICODE_STRING objname;
IO_STATUS_BLOCK IoStatusBlock;
ULONG mp_flags;
//
// copy user parameters into consolidated buffer: dir"\"name
//
path_len = user_dir_len + user_name_len + sizeof(WCHAR) * 8;
path = Dll_AllocTemp(path_len);
if (! path)
return STATUS_INSUFFICIENT_RESOURCES;
memzero(path, path_len);
memcpy(path, user_dir, user_dir_len);
name = path + wcslen(path);
*name = L'\\';
memcpy(&name[1], user_name, user_name_len);
if (wcschr(&name[1], L'\\'))
return STATUS_INVALID_PARAMETER;
//
// check if the full target path is an open path, and stop if not
//
mp_flags = File_MatchPath(path, NULL);
if (!PATH_IS_OPEN(mp_flags) || PATH_IS_CLOSED(mp_flags)) {
Dll_Free(path);
return STATUS_BAD_INITIAL_PC;
}
//
// check if the target directory is an open path, and stop if it is
//
*name = L'\0';
mp_flags = File_MatchPath(path, NULL);
if (PATH_IS_OPEN(mp_flags) || PATH_IS_CLOSED(mp_flags)) {
Dll_Free(path);
return STATUS_BAD_INITIAL_PC;
}
//
// now we have established that the full target path name is an
// open path, but the parent directory in that path isn't open.
// therefore we will open the parent directory for write access
// from kernel mode, and do the rename here
//
// we put a the trailing backslash on the path, so that we can open
// the parent directory even when the parent is the root directory
//
save_char = name[1];
name[0] = L'\\';
name[1] = L'\0';
InitializeObjectAttributes(
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
RtlInitUnicodeString(&objname, path);
status = __sys_NtCreateFile(
&dir_handle, FILE_GENERIC_WRITE, &objattrs,
&IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS,
FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (! NT_SUCCESS(status)) {
Dll_Free(path);
return status;
}
//
// allocate an information buffer, and issue rename request
//
++name;
*name = save_char;
name_len = wcslen(name) * sizeof(WCHAR);
info_len = sizeof(FILE_RENAME_INFORMATION) + name_len + 8;
info = Dll_AllocTemp(info_len);
if (! info)
status = STATUS_INSUFFICIENT_RESOURCES;
else {
memzero(info, info_len);
info->ReplaceIfExists = replace_if_exists;
info->RootDirectory = dir_handle;
info->FileNameLength = name_len;
memcpy(info->FileName, name, name_len);
if (NT_SUCCESS(status)) {
status = __sys_NtSetInformationFile(
file_handle, &IoStatusBlock, //args->file_handle.val, &IoStatusBlock,
info, info_len, FileRenameInformation);
}
// FIXME, we may get STATUS_NOT_SAME_DEVICE, however, in most cases,
// this API call is used to rename a file inside a folder, rather
// than move files across folders, so that isn't a problem
Dll_Free(info);
}
NtClose(dir_handle);
Dll_Free(path);
return status;
}
//---------------------------------------------------------------------------
// File_RenameFile
//---------------------------------------------------------------------------
_FX NTSTATUS File_RenameFile(
HANDLE FileHandle, FILE_RENAME_INFORMATION *info)
{
THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
NTSTATUS status;
OBJECT_ATTRIBUTES objattrs;
UNICODE_STRING objname;
IO_STATUS_BLOCK IoStatusBlock;
WCHAR *TruePath;
WCHAR *CopyPath;
HANDLE SourceHandle, TargetHandle;
WCHAR *SourceTruePath;
WCHAR *SourceCopyPath;
WCHAR *TargetTruePath;
WCHAR *TargetCopyPath;
WCHAR *TargetFileName;
WCHAR *ReparsedPath;
WCHAR save_char;
ULONG info2_len;
FILE_RENAME_INFORMATION *info2;
FILE_NETWORK_OPEN_INFORMATION open_info;
ULONG SourceFlags;
ULONG TargetFlags;
ULONG len;
SourceHandle = NULL;
TargetHandle = NULL;
SourceTruePath = NULL;
SourceCopyPath = NULL;
TargetTruePath = NULL;
TargetCopyPath = NULL;
info2 = NULL;
Dll_PushTlsNameBuffer(TlsData);
InitializeObjectAttributes(
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, Secure_NormalSD);
__try {
//
// get the name of the file to be renamed
//
RtlInitUnicodeString(&objname, L"");
status = File_GetName(FileHandle, &objname, &TruePath, &CopyPath, NULL);
if (! NT_SUCCESS(status))
__leave;
//
// open the file for write access. this should cause the file
// to be migrated into the sandbox, including its parent directories
//
RtlInitUnicodeString(&objname, TruePath);
++TlsData->file_dont_strip_write_access;
status = NtCreateFile(
&SourceHandle, FILE_GENERIC_WRITE | DELETE, &objattrs,
&IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS,
FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (status == STATUS_SHARING_VIOLATION ||
status == STATUS_ACCESS_DENIED) {
//
// Windows Mail opens *.eml files with a combination of
// FILE_SHARE_READ | FILE_SHARE_DELETE, but not FILE_SHARE_WRITE,
// which means we can't open them with FILE_GENERIC_WRITE
// during rename processing here
//
// also, for read-only files, we get an error when we open them
// for FILE_GENERIC_WRITE, but just DELETE should also work
//
status = NtCreateFile(
&SourceHandle, SYNCHRONIZE | DELETE, &objattrs,
&IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS,
FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
//
// if we still get STATUS_SHARING_VIOLATION, give up on trying
// to make sure the file is migrated into the sandbox, and hope
// that the input FileHandle is suitable for a rename operation
//
if (status == STATUS_SHARING_VIOLATION) {
SourceHandle = FileHandle;
status = STATUS_SUCCESS;
}
}
--TlsData->file_dont_strip_write_access;
if (! NT_SUCCESS(status))
__leave;
//
// get the name of the opened handle, again. if it is still
// outside the box, it must be an open path
//
RtlInitUnicodeString(&objname, L"");
status = File_GetName(
SourceHandle, &objname, &TruePath, &CopyPath, &SourceFlags);
if (! NT_SUCCESS(status))
__leave;
//
// save a copy of the source path:
//
// 1. if this isn't an open path, we will need to re-create
// the source path as empty file, marked deleted before
// we finish
//
// 2. if ReplaceIfExists is not specified, we have to check
// if the source and target are the same path
//
len = (wcslen(TruePath) + 1) * sizeof(WCHAR);
SourceTruePath = Dll_AllocTemp(len);
memcpy(SourceTruePath, TruePath, len);
len = (wcslen(CopyPath) + 1) * sizeof(WCHAR);
SourceCopyPath = Dll_AllocTemp(len);
memcpy(SourceCopyPath, CopyPath, len);
//
// get the target name requested by the caller, and keep
// duplicated strings, because any call to NtCreateFile may
// overwrite the shared name buffers
//
objname.Length = (USHORT)info->FileNameLength;
objname.MaximumLength = objname.Length;
objname.Buffer = info->FileName;
status = File_GetName(
info->RootDirectory, &objname, &TruePath, &CopyPath, &TargetFlags);
if (! NT_SUCCESS(status))
__leave;
ReparsedPath = File_TranslateTempLinks(TruePath, FALSE);
if (ReparsedPath) {
len = (wcslen(ReparsedPath) + 1) * sizeof(WCHAR);
TargetTruePath = Dll_AllocTemp(len);
memcpy(TargetTruePath, ReparsedPath, len);
Dll_Free(ReparsedPath);
} else {
len = (wcslen(TruePath) + 1) * sizeof(WCHAR);
TargetTruePath = Dll_AllocTemp(len);
memcpy(TargetTruePath, TruePath, len);
}
len = (wcslen(CopyPath) + 1) * sizeof(WCHAR);
TargetCopyPath = Dll_AllocTemp(len);
memcpy(TargetCopyPath, CopyPath, len);
//
// separate the true path into directory and filename portions
//
TargetFileName = wcsrchr(TargetTruePath, L'\\');
if (wcschr(TargetFileName, L':')) {
status = STATUS_INVALID_PARAMETER;
__leave;
}
++TargetFileName;
//
// if the full path name for the target is an open path, we want
// to be able to rename outside the sandbox. however, the parent
// directory in that full path may not be an open path itself.
// invoke the driver to do such a rename on our behalf
//
TargetFileName[-1] = L'\0';
ReparsedPath = File_FixPermLinksForMatchPath(TargetTruePath);
if (! ReparsedPath)
ReparsedPath = TargetTruePath;
//if (!Dll_CompartmentMode) // NoDriverAssist
status = SbieApi_RenameFile(SourceHandle, ReparsedPath, TargetFileName, info->ReplaceIfExists);
//else
// status = File_RenameOpenFile(SourceHandle, ReparsedPath, TargetFileName, info->ReplaceIfExists);
if (ReparsedPath != TargetTruePath)
Dll_Free(ReparsedPath);
TargetFileName[-1] = L'\\';
if (status != STATUS_BAD_INITIAL_PC) {
if (NT_SUCCESS(status))
goto after_rename;
__leave;
}
//
// open the parent directory of the target path name of the rename.
// this should cause the directory to be created in the sandbox,
// or return a handle to the true directory, if it is an open path
//
// we keep the trailing backslash on the path, so that we can open
// the parent directory even when the parent is the root directory
//
save_char = *TargetFileName;
*TargetFileName = L'\0';
RtlInitUnicodeString(&objname, TargetTruePath);
++TlsData->file_dont_strip_write_access;
status = NtCreateFile(
&TargetHandle, FILE_GENERIC_WRITE, &objattrs,
&IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS, FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE, NULL, 0);
if (status == STATUS_OBJECT_NAME_NOT_FOUND) {
//
// we got "file not found" when opening the parent directory,
// so we really need to return a "path not found" error code
//
status = STATUS_OBJECT_PATH_NOT_FOUND;
} else if (status == STATUS_ACCESS_DENIED) {
//
// for hidden/system/read-only directories we can get access
// denied, so try to explicitly create the copy directory.
// if that also fails, then open the directory read-only
//
WCHAR *TargetCopyPtr = wcsrchr(TargetCopyPath, L'\\') + 1;
WCHAR save_char_copy = *TargetCopyPtr;
*TargetCopyPtr = L'\0';
File_CreatePath(TargetTruePath, TargetCopyPath);
*TargetCopyPtr = save_char_copy;
status = NtCreateFile(
&TargetHandle, FILE_GENERIC_WRITE, &objattrs,
&IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS, FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE, NULL, 0);
if (status == STATUS_ACCESS_DENIED) {
status = NtCreateFile(
&TargetHandle, FILE_GENERIC_READ, &objattrs,
&IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS, FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE, NULL, 0);
}
}
--TlsData->file_dont_strip_write_access;
*TargetFileName = save_char;
if (! NT_SUCCESS(status))
__leave;
//
// allocate a new information buffer
//
info2_len = sizeof(FILE_RENAME_INFORMATION)
+ wcslen(TargetFileName) * sizeof(WCHAR);
info2 = Dll_AllocTemp(info2_len);
info2->ReplaceIfExists = info->ReplaceIfExists;
info2->RootDirectory = TargetHandle;
info2->FileNameLength = wcslen(TargetFileName) * sizeof(WCHAR);
memcpy(info2->FileName, TargetFileName, info2->FileNameLength);
//
// if the source and target paths are the same (in a case
// insensitive compare), then skip the following check which
// might result in the possible deletion of the source path
//
if (_wcsicmp(SourceTruePath, TargetTruePath) == 0) {
goto issue_rename;
}
//
// if the caller requested to replace the destination file,
// then physically delete it first. we expect this may fail:
// if the file does not actually exist, or if it exists only
// outside the sandbox in a directory that isn't open.
//
RtlInitUnicodeString(&objname, TargetCopyPath);
if (! info2->ReplaceIfExists) {
//
// if caller did not explicitly ask to replace, but the
// destination path name is marked deleted, then we also
// physically delete the destination
//
status = __sys_NtQueryFullAttributesFile(&objattrs, &open_info);
if (NT_SUCCESS(status)) {
if (IS_DELETE_MARK(&open_info.CreationTime)) { // !File_Delete_v2 &&
info2->ReplaceIfExists = TRUE;
} else {
status = STATUS_OBJECT_NAME_COLLISION;
__leave;
}
} else {
WCHAR* TargetTruePath2 = TargetTruePath;
ULONG TargetTruePathFlags = 0;
WCHAR* OldTruePath = File_ResolveTruePath(TargetTruePath, TargetCopyPath, &TargetTruePathFlags);
if (OldTruePath)
TargetTruePath2 = OldTruePath;
RtlInitUnicodeString(&objname, TargetTruePath2);
if (FILE_PATH_DELETED(TargetTruePathFlags)) // File_Delete_v2 &&
{
status = STATUS_OBJECT_NAME_NOT_FOUND;
}
// $Workaround$ - 3rd party fix
else if (!Dll_DigitalGuardian)
{
status = __sys_NtQueryFullAttributesFile(&objattrs, &open_info);
}
else
{
ULONG mp_flags = File_MatchPath(TargetTruePath2, &TargetFlags);
if (PATH_IS_OPEN(mp_flags) || !mp_flags)
{
// check true path exist
status = __sys_NtQueryFullAttributesFile(&objattrs, &open_info);
}
else
{
// check copy path exist
RtlInitUnicodeString(&objname, TargetCopyPath);
status = __sys_NtQueryFullAttributesFile(&objattrs, &open_info);
}
}
if (NT_SUCCESS(status)) {
status = STATUS_OBJECT_NAME_COLLISION;
__leave;
}
RtlInitUnicodeString(&objname, TargetCopyPath);
}
}
if (info2->ReplaceIfExists) {
__sys_NtDeleteFile(&objattrs);
}
//
// issue the rename request
//
issue_rename:
status = __sys_NtSetInformationFile(
SourceHandle, &IoStatusBlock,
info2, info2_len, FileRenameInformation);
if (status == STATUS_SHARING_VIOLATION && SourceHandle != FileHandle) {
//
// in case opening that the second SourceHandle prevents the
// rename from succeeding, try again with FileHandle (assuming
// it was opened with DELETE access)
//
NtClose(SourceHandle);
SourceHandle = FileHandle;
status = __sys_NtSetInformationFile(
SourceHandle, &IoStatusBlock,
info2, info2_len, FileRenameInformation);
}
if (! NT_SUCCESS(status)) {
// FIXME, we may get STATUS_NOT_SAME_DEVICE here, if the rename
// involves an OpenFilePath, however, in most cases, this call
// is coming from kernel32!MoveFileXxx, which is smart enough
// to copy a file when it can't be renamed (MOVEFILE_COPY_ALLOWED)
if (! NT_SUCCESS(status))
__leave;
}
NtClose(TargetHandle);
TargetHandle = NULL;
//
// in the case the target file was marked deleted in the sandbox,
// make sure the delete mark is overwritten. we're working on the
// target copy path, so we expect the NtCreateFile may fail if this
// is an open path
//
if (!File_Delete_v2) {
RtlInitUnicodeString(&objname, TargetCopyPath);
status = __sys_NtCreateFile(
&TargetHandle, FILE_READ_ATTRIBUTES | SYNCHRONIZE,
&objattrs, &IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS,
FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (NT_SUCCESS(status)) {
File_SetCreateTime(TargetHandle, TargetCopyPath);
NtClose(TargetHandle);
TargetHandle = NULL;
}
else
status = STATUS_SUCCESS;
}
//
// record for recovery
//
File_RecordRecover(FileHandle, TargetTruePath);
//
// if the source file exists in the sandbox, we need to create an
// empty file, marked deleted, in place of the old name
//
after_rename:
if (SourceFlags & FGN_IS_BOXED_PATH) {
NTSTATUS status2;
WCHAR* SourceTruePath2 = SourceTruePath;
WCHAR* OldTruePath = File_ResolveTruePath(SourceTruePath, SourceCopyPath, NULL);
if (OldTruePath)
SourceTruePath2 = OldTruePath;
RtlInitUnicodeString(&objname, SourceTruePath2);
status2 = __sys_NtQueryFullAttributesFile(&objattrs, &open_info);
if (File_Delete_v2) {
BOOLEAN TrueExists = FALSE;
BOOLEAN IsDirectroy;
if (NT_SUCCESS(status2)) {
//
// if this file exist in the true path mark it as deleted,
// directories are handled by File_SetRelocation
//
TrueExists = TRUE;
IsDirectroy = (open_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
if (!IsDirectroy)
File_MarkDeleted_v2(SourceTruePath);
}
else {
//
// if it does not exist check if it was a directory, it may be a boxed directory
// which is a relocation target in which case we will need to update the relocation data
//
IO_STATUS_BLOCK IoStatusBlock;
FILE_BASIC_INFORMATION info3;
status2 = __sys_NtQueryInformationFile(
FileHandle, &IoStatusBlock, &info3,
sizeof(FILE_BASIC_INFORMATION), FileBasicInformation);
IsDirectroy = (info3.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
//
// if this is a directory and if so update/create the appropriate remapping
//
if (TrueExists && IsDirectroy) {
File_SetRelocation(SourceTruePath, TargetTruePath);
}
}
else
if (NT_SUCCESS(status2)) {
HANDLE handle2;
//
// mark deleted only if there is a corresponding file
// outside the sandbox
//
RtlInitUnicodeString(&objname, SourceCopyPath);
status2 = __sys_NtCreateFile(
&handle2, FILE_GENERIC_READ, &objattrs,
&IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS,
FILE_CREATE,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
NULL, 0);
if (NT_SUCCESS(status2)) {
File_MarkDeleted(handle2, SourceCopyPath);
NtClose(handle2);
}
}
}
//
// finish
//
} __except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
Dll_PopTlsNameBuffer(TlsData);
if (SourceHandle && SourceHandle != FileHandle)
NtClose(SourceHandle);
if (TargetHandle)
NtClose(TargetHandle);
if (SourceTruePath)
Dll_Free(SourceTruePath);
if (SourceCopyPath)
Dll_Free(SourceCopyPath);
if (TargetTruePath)
Dll_Free(TargetTruePath);
if (TargetCopyPath)
Dll_Free(TargetCopyPath);
if (info2)
Dll_Free(info2);
return status;
}
//---------------------------------------------------------------------------
// File_GetTrueHandle
//---------------------------------------------------------------------------
_FX HANDLE File_GetTrueHandle(HANDLE FileHandle, BOOLEAN *pIsOpenPath)
{
ULONG LastError;
THREAD_DATA *TlsData = Dll_GetTlsData(&LastError);
NTSTATUS status;
OBJECT_ATTRIBUTES objattrs;
UNICODE_STRING objname;
WCHAR *TruePath;
WCHAR *CopyPath;
IO_STATUS_BLOCK IoStatusBlock;
HANDLE handle = NULL;
ULONG FileFlags;
Dll_PushTlsNameBuffer(TlsData);
//
// get file path for the handle
//
if (pIsOpenPath)
*pIsOpenPath = FALSE;
RtlInitUnicodeString(&objname, L"");
InitializeObjectAttributes(
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
status = File_GetName(
FileHandle, &objname, &TruePath, &CopyPath, &FileFlags);
if (NT_SUCCESS(status)) {
//
// check if this is an open or closed path
//
ULONG mp_flags = File_MatchPath(TruePath, &FileFlags);
if (PATH_IS_OPEN(mp_flags) && pIsOpenPath)
*pIsOpenPath = TRUE;
if (! mp_flags) {
//
// open file
//
RtlInitUnicodeString(&objname, TruePath);
status = __sys_NtCreateFile(
&handle, FILE_GENERIC_READ, &objattrs,
&IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS,
FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (! NT_SUCCESS(status))
handle = NULL;
}
}
Dll_PopTlsNameBuffer(TlsData);
SetLastError(LastError);
return handle;
}
//---------------------------------------------------------------------------
// SbieDll_GetHandlePath
//---------------------------------------------------------------------------
_FX ULONG SbieDll_GetHandlePath(
HANDLE FileHandle, WCHAR *OutWchar8192, BOOLEAN *IsBoxedPath)
{
THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
NTSTATUS status;
ULONG FileFlags;
WCHAR *TruePath;
WCHAR *CopyPath;
SbieDll_GetDrivePath(0); // initialize drives
Dll_PushTlsNameBuffer(TlsData);
//
// This function returns actual paths as they exist in the real filesystem
// copy paths may point to the snapshot if the file is there
// and true paths will point to the original location if they're redirected
// therefore calling hooked file functions on these paths may run into file not found
// when the original location is marked as deleted
//
status = File_GetName(
FileHandle, NO_RELOCATION, &TruePath, &CopyPath, &FileFlags);
if (IsBoxedPath) {
if (FileFlags & FGN_IS_BOXED_PATH)
*IsBoxedPath = TRUE;
else
*IsBoxedPath = FALSE;
} else if (status == STATUS_BAD_INITIAL_PC)
status = STATUS_SUCCESS;
if (NT_SUCCESS(status) && OutWchar8192) {
ULONG len;
WCHAR *src = TruePath;
if (Dll_BoxName && // sandboxed process
IsBoxedPath && *IsBoxedPath) {
src = CopyPath;
}
len = wcslen(src);
if (len > 8192 / sizeof(WCHAR) - 4)
len = 8192 / sizeof(WCHAR) - 4;
wmemcpy(OutWchar8192, src, len);
OutWchar8192[len] = L'\0';
}
Dll_PopTlsNameBuffer(TlsData);
return status;
}
//---------------------------------------------------------------------------
// SbieDll_GetDrivePath
//---------------------------------------------------------------------------
_FX const WCHAR *SbieDll_GetDrivePath(ULONG DriveIndex)
{
if ((! File_Drives) || (DriveIndex == -1)) {
File_InitDrives(0xFFFFFFFF);
if (DriveIndex == -1)
return NULL;
}
if (File_Drives && (DriveIndex < 26) && File_Drives[DriveIndex])
return File_Drives[DriveIndex]->path;
return NULL;
}
//---------------------------------------------------------------------------
// SbieDll_GetUserPathEx
//---------------------------------------------------------------------------
_FX const WCHAR *SbieDll_GetUserPathEx(WCHAR which)
{
if (! Dll_SidString) {
UNICODE_STRING SidString;
NTSTATUS status = Dll_GetCurrentSidString(&SidString);
if (NT_SUCCESS(status))
Dll_SidString = SidString.Buffer;
if (! Dll_SidString)
return NULL;
}
if (! File_CurrentUser) {
SbieDll_GetDrivePath(0); // initialize drives
File_InitUsers();
}
if (which == L'a')
return File_AllUsers;
else if (which == L'c')
return File_CurrentUser;
else if (which == L'p')
return File_PublicUser;
return NULL;
}
//---------------------------------------------------------------------------
// SbieDll_TranslateNtToDosPath
//---------------------------------------------------------------------------
_FX BOOLEAN SbieDll_TranslateNtToDosPath(WCHAR *path)
{
const FILE_DRIVE *drive;
ULONG path_len, prefix_len;
if (_wcsnicmp(path, L"\\??\\", 4) == 0) {
wmemmove(path, path + 4, wcslen(path) - 4 + 1);
return TRUE;
}
if (! File_DrivesAndLinks_CritSec) { // if not sandboxed
File_DrivesAndLinks_CritSec = Dll_Alloc(sizeof(CRITICAL_SECTION));
InitializeCriticalSectionAndSpinCount(
File_DrivesAndLinks_CritSec, 1000);
SbieDll_GetDrivePath(0); // initialize drives
}
if (_wcsnicmp(path, File_Mup, File_MupLen) == 0) {
WCHAR *ptr = path + File_MupLen - 1;
wmemmove(path + 1, ptr, wcslen(ptr) + 1);
return TRUE;
}
path_len = wcslen(path);
drive = File_GetDriveForPath(path, path_len);
if (drive)
prefix_len = drive->len;
else
drive = File_GetDriveForUncPath(path, path_len, &prefix_len);
if (drive) {
WCHAR drive_letter = drive->letter;
WCHAR *ptr = path + prefix_len;
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
if (*ptr == L'\\' || *ptr == L'\0') {
wmemmove(path + 2, ptr, wcslen(ptr) + 1);
path[0] = drive_letter;
path[1] = L':';
}
return TRUE;
}
return FALSE;
}
//---------------------------------------------------------------------------
// File_GetTruePathForBoxedPath
//---------------------------------------------------------------------------
_FX WCHAR *File_GetTruePathForBoxedPath(const WCHAR *Path, BOOLEAN IsDosPath)
{
WCHAR *NtPath;
WCHAR *TruePathResult = NULL;
if (IsDosPath)
NtPath = File_TranslateDosToNtPath(Path);
else
NtPath = (WCHAR *)Path;
if (NtPath) {
if (_wcsnicmp(NtPath, Dll_BoxFilePath, Dll_BoxFilePathLen) == 0) {
NTSTATUS status;
UNICODE_STRING uni;
WCHAR *TruePath, *CopyPath;
THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
RtlInitUnicodeString(&uni, NtPath);
Dll_PushTlsNameBuffer(TlsData);
status = File_GetName(NULL, &uni, &TruePath, &CopyPath, NULL);
if (status == STATUS_BAD_INITIAL_PC) {
//
// add suffix backslash to c:\sandbox\...\drive\x so
// File_GetName doesn't think we try to open a device
//
ULONG len = wcslen(NtPath);
WCHAR *BackslashPath =
Dll_AllocTemp((len + 2) * sizeof(WCHAR));
wmemcpy(BackslashPath, NtPath, len);
BackslashPath[len] = L'\\';
BackslashPath[len + 1] = L'\0';
RtlInitUnicodeString(&uni, BackslashPath);
status =
File_GetName(NULL, &uni, &TruePath, &CopyPath, NULL);
Dll_Free(BackslashPath);
}
if (NT_SUCCESS(status)) {
if (IsDosPath) {
if (! SbieDll_TranslateNtToDosPath(TruePath))
TruePath = NULL;
}
} else
TruePath = NULL;
if (TruePath) {
ULONG len = (wcslen(TruePath) + 1) * sizeof(WCHAR);
TruePathResult = Dll_Alloc(len);
memcpy(TruePathResult, TruePath, len);
}
Dll_PopTlsNameBuffer(TlsData);
}
if (NtPath != Path)
Dll_Free(NtPath);
}
return TruePathResult;
}
//---------------------------------------------------------------------------
// SbieDll_DeviceChange
//---------------------------------------------------------------------------
_FX void SbieDll_DeviceChange(WPARAM wParam, LPARAM lParam)
{
static ULONG LastTickCount = 0;
static ULONG LastWParam = 0;
static ULONG LastDriveMask = 0;
if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) {
DEV_BROADCAST_HDR *hdr = (DEV_BROADCAST_HDR *)lParam;
if (hdr->dbch_devicetype == DBT_DEVTYP_VOLUME) {
DEV_BROADCAST_VOLUME *vol = (DEV_BROADCAST_VOLUME *)hdr;
if (! (vol->dbcv_flags & DBTF_MEDIA)) {
ULONG ThisTickCount = GetTickCount();
ULONG ThisDriveMask = vol->dbcv_unitmask;
if ((wParam != LastWParam) ||
(ThisDriveMask != LastDriveMask) ||
((ThisTickCount - LastTickCount) > 500)) {
File_InitDrives(ThisDriveMask);
Dll_RefreshPathList();
}
LastTickCount = ThisTickCount;
LastWParam = (ULONG)wParam;
LastDriveMask = ThisDriveMask;
}
}
} else if ((wParam & 0xFF80) == 0xAA00 && lParam == tzuk) { // see NetApi_NetUseAdd
UCHAR drive_number = (UCHAR)(wParam & 0x1F);
if (drive_number < 26) {
File_InitDrives(1 << drive_number);
Dll_RefreshPathList();
}
} else if (wParam == 'sb' && lParam == 0) {
Dll_RefreshPathList();
}
}
//---------------------------------------------------------------------------
// SbieDll_QueryFileAttributes
//---------------------------------------------------------------------------
BOOL SbieDll_QueryFileAttributes(const WCHAR *NtPath, ULONG64 *size, ULONG64 *date, ULONG *attrs)
{
NTSTATUS status;
UNICODE_STRING uni;
OBJECT_ATTRIBUTES objattrs;
FILE_NETWORK_OPEN_INFORMATION info;
uni.Buffer = (WCHAR *)NtPath;
uni.Length = wcslen(NtPath) * sizeof(WCHAR);
uni.MaximumLength = uni.Length + sizeof(WCHAR);
InitializeObjectAttributes(
&objattrs, &uni, OBJ_CASE_INSENSITIVE, NULL, NULL);
if(__sys_NtQueryFullAttributesFile)
status = __sys_NtQueryFullAttributesFile(&objattrs, &info);
else
status = NtQueryFullAttributesFile(&objattrs, &info);
if (! NT_SUCCESS(status))
return FALSE;
if(size) *size = info.EndOfFile.QuadPart;
if(date) *date = info.LastWriteTime.QuadPart;
if(attrs) *attrs = info.FileAttributes;
return TRUE;
}
// We don't want calls to StopTailCallOptimization to be optimized away
#pragma optimize("", off)
_FX NTSTATUS StopTailCallOptimization(NTSTATUS status)
{
return status;
}
// $Workaround$ - 3rd party fix
_FX BOOLEAN DigitalGuardian_Init(HMODULE hModule)
{
Dll_DigitalGuardian = hModule;
return TRUE;
}