mirror of
https://github.com/lahfir/agent-desktop.git
synced 2026-08-01 03:40:58 +00:00
Answers the four questions sub-phase 2.1 rebuilds its private-file layer against, measured on the real OS with verbatim Win32 error codes. Three of the four contradict the framing this work was planned under. Rename: MoveFileEx cannot replace a target that has any open handle, and it reports ERROR_ACCESS_DENIED rather than the sharing violation the plan assumed -- FILE_SHARE_DELETE does not rescue it, because MoveFileEx issues ReplaceIfExists rather than POSIX-semantics rename. ReplaceFile is the operation that honors share-delete on the destination, but it is the one that refuses an open handle on the replacement source. The two APIs have opposite tolerances on opposite sides, so code treating error 32 as the sharing case misclassifies the most likely production failure. Ownership: new files take their owner from TokenOwner, never TokenUser, at both High and Medium integrity. Integrity level is not the variable -- the default follows admin-group membership, so an owner-only DACL check breaks for any admin account regardless of elevation. Locality: GetFileInformationByHandleEx(FileRemoteProtocolInfo) does distinguish local from remote, but signals local by failing with ERROR_INVALID_PARAMETER rather than succeeding with a local protocol value. An out-of-range info class returns that same error, so the code is ambiguous and a control call is required before reading it as a locality signal. ACL: neither leaf carries an explicit ACE. The restriction is authored at the nearest protected ancestor and merely propagated, so a leaf-only check sees the effective grants but cannot tell whether they are anchored or silently re-derivable, and cannot see FILE_DELETE_CHILD on the parent.
293 lines
13 KiB
C#
293 lines
13 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace AgentDesktopProbe {
|
|
|
|
public class HandleResult {
|
|
public long Handle;
|
|
public bool Success;
|
|
public int LastError;
|
|
public string ErrorName;
|
|
public string Message;
|
|
}
|
|
|
|
public class OpResult {
|
|
public bool Success;
|
|
public int LastError;
|
|
public string ErrorName;
|
|
public string Message;
|
|
public string Detail;
|
|
}
|
|
|
|
public class RemoteProtocolResult {
|
|
public string Target;
|
|
public bool OpenSuccess;
|
|
public int OpenError;
|
|
public string OpenErrorName;
|
|
public bool ControlCallSuccess;
|
|
public int ControlLastError;
|
|
public string ControlErrorName;
|
|
public string ControlDetail;
|
|
public int InfoClass;
|
|
public bool OutOfRangeClassCallSuccess;
|
|
public int OutOfRangeClassLastError;
|
|
public string OutOfRangeClassErrorName;
|
|
public bool CallSuccess;
|
|
public int LastError;
|
|
public string ErrorName;
|
|
public string Message;
|
|
public int StructureVersion;
|
|
public int StructureSize;
|
|
public string Protocol;
|
|
public string ProtocolName;
|
|
public int ProtocolMajorVersion;
|
|
public int ProtocolMinorVersion;
|
|
public int ProtocolRevision;
|
|
public string Flags;
|
|
public string FlagNames;
|
|
public int DriveType;
|
|
public string DriveTypeName;
|
|
public bool PathIsNetworkPath;
|
|
}
|
|
|
|
public static class PrivateFile {
|
|
|
|
private const uint OPEN_EXISTING = 3;
|
|
private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
|
|
private const int FileBasicInfo = 0;
|
|
private const int FileRemoteProtocolInfo = 13;
|
|
private const int OutOfRangeInfoClass = 55;
|
|
private static readonly IntPtr InvalidHandle = new IntPtr(-1);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
private static extern IntPtr CreateFileW(string lpFileName, uint dwDesiredAccess, uint dwShareMode,
|
|
IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
private static extern bool MoveFileExW(string lpExistingFileName, string lpNewFileName, uint dwFlags);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
private static extern bool ReplaceFileW(string lpReplacedFileName, string lpReplacementFileName,
|
|
string lpBackupFileName, uint dwReplaceFlags, IntPtr lpExclude, IntPtr lpReserved);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern bool GetFileInformationByHandleEx(IntPtr hFile, int FileInformationClass,
|
|
IntPtr lpFileInformation, uint dwBufferSize);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern bool ReadFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToRead,
|
|
out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern uint SetFilePointer(IntPtr hFile, int lDistanceToMove, IntPtr lpDistanceToMoveHigh, uint dwMoveMethod);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern bool CloseHandle(IntPtr hObject);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
private static extern uint GetDriveTypeW(string lpRootPathName);
|
|
|
|
[DllImport("shlwapi.dll", SetLastError = false, CharSet = CharSet.Unicode)]
|
|
private static extern bool PathIsNetworkPathW(string pszPath);
|
|
|
|
public static string ErrorName(int code) {
|
|
switch (code) {
|
|
case 0: return "ERROR_SUCCESS";
|
|
case 2: return "ERROR_FILE_NOT_FOUND";
|
|
case 3: return "ERROR_PATH_NOT_FOUND";
|
|
case 5: return "ERROR_ACCESS_DENIED";
|
|
case 6: return "ERROR_INVALID_HANDLE";
|
|
case 19: return "ERROR_WRITE_PROTECT";
|
|
case 32: return "ERROR_SHARING_VIOLATION";
|
|
case 33: return "ERROR_LOCK_VIOLATION";
|
|
case 50: return "ERROR_NOT_SUPPORTED";
|
|
case 53: return "ERROR_BAD_NETPATH";
|
|
case 80: return "ERROR_FILE_EXISTS";
|
|
case 87: return "ERROR_INVALID_PARAMETER";
|
|
case 183: return "ERROR_ALREADY_EXISTS";
|
|
case 995: return "ERROR_OPERATION_ABORTED";
|
|
case 1005: return "ERROR_UNRECOGNIZED_VOLUME";
|
|
case 1176: return "ERROR_UNABLE_TO_MOVE_REPLACEMENT";
|
|
case 1177: return "ERROR_UNABLE_TO_MOVE_REPLACEMENT_2";
|
|
case 1178: return "ERROR_UNABLE_TO_REMOVE_REPLACED";
|
|
case 4390: return "ERROR_NOT_A_REPARSE_POINT";
|
|
default: return "ERROR_" + code;
|
|
}
|
|
}
|
|
|
|
private static string SystemMessage(int code) {
|
|
try {
|
|
return new Win32Exception(code).Message;
|
|
} catch (Exception) {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
private static string DriveTypeName(uint value) {
|
|
switch (value) {
|
|
case 0: return "DRIVE_UNKNOWN";
|
|
case 1: return "DRIVE_NO_ROOT_DIR";
|
|
case 2: return "DRIVE_REMOVABLE";
|
|
case 3: return "DRIVE_FIXED";
|
|
case 4: return "DRIVE_REMOTE";
|
|
case 5: return "DRIVE_CDROM";
|
|
case 6: return "DRIVE_RAMDISK";
|
|
default: return "DRIVE_" + value;
|
|
}
|
|
}
|
|
|
|
private static string ProtocolName(uint value) {
|
|
switch (value) {
|
|
case 0x00020000: return "WNNC_NET_SMB";
|
|
case 0x00420000: return "WNNC_NET_MS_NFS";
|
|
case 0x00430000: return "WNNC_NET_MS_DAV";
|
|
case 0x00000000: return "WNNC_NET_NONE";
|
|
default: return "0x" + value.ToString("X8");
|
|
}
|
|
}
|
|
|
|
private static string FlagNames(uint value) {
|
|
string names = "";
|
|
if ((value & 0x00000001) != 0) { names = names + "LOOPBACK|"; }
|
|
if ((value & 0x00000002) != 0) { names = names + "OFFLINE|"; }
|
|
if ((value & 0x00000004) != 0) { names = names + "PERSISTENT_HANDLE|"; }
|
|
if ((value & 0x00000008) != 0) { names = names + "PRIVACY|"; }
|
|
if ((value & 0x00000010) != 0) { names = names + "INTEGRITY|"; }
|
|
if ((value & 0x00000020) != 0) { names = names + "MUTUAL_AUTH|"; }
|
|
if (names.Length == 0) { return "NONE"; }
|
|
return names.Substring(0, names.Length - 1);
|
|
}
|
|
|
|
public static HandleResult OpenHandle(string path, uint access, uint share, uint flags) {
|
|
HandleResult result = new HandleResult();
|
|
IntPtr h = CreateFileW(path, access, share, IntPtr.Zero, OPEN_EXISTING, flags, IntPtr.Zero);
|
|
int err = Marshal.GetLastWin32Error();
|
|
if (h == InvalidHandle) {
|
|
result.Handle = -1;
|
|
result.Success = false;
|
|
result.LastError = err;
|
|
result.ErrorName = ErrorName(err);
|
|
result.Message = SystemMessage(err);
|
|
return result;
|
|
}
|
|
result.Handle = h.ToInt64();
|
|
result.Success = true;
|
|
result.LastError = 0;
|
|
result.ErrorName = ErrorName(0);
|
|
result.Message = "";
|
|
return result;
|
|
}
|
|
|
|
public static bool CloseFileHandle(long handle) {
|
|
if (handle == 0 || handle == -1) { return false; }
|
|
return CloseHandle(new IntPtr(handle));
|
|
}
|
|
|
|
public static OpResult ReadFromHandle(long handle, int count) {
|
|
OpResult result = new OpResult();
|
|
IntPtr h = new IntPtr(handle);
|
|
SetFilePointer(h, 0, IntPtr.Zero, 0);
|
|
byte[] buffer = new byte[count];
|
|
uint read = 0;
|
|
bool ok = ReadFile(h, buffer, (uint)count, out read, IntPtr.Zero);
|
|
int err = Marshal.GetLastWin32Error();
|
|
result.Success = ok;
|
|
result.LastError = ok ? 0 : err;
|
|
result.ErrorName = ErrorName(ok ? 0 : err);
|
|
result.Message = ok ? "" : SystemMessage(err);
|
|
result.Detail = ok ? System.Text.Encoding.ASCII.GetString(buffer, 0, (int)read) : "";
|
|
return result;
|
|
}
|
|
|
|
public static OpResult MoveOver(string source, string target, uint flags) {
|
|
OpResult result = new OpResult();
|
|
bool ok = MoveFileExW(source, target, flags);
|
|
int err = Marshal.GetLastWin32Error();
|
|
result.Success = ok;
|
|
result.LastError = ok ? 0 : err;
|
|
result.ErrorName = ErrorName(ok ? 0 : err);
|
|
result.Message = ok ? "" : SystemMessage(err);
|
|
result.Detail = "MoveFileExW flags=0x" + flags.ToString("X");
|
|
return result;
|
|
}
|
|
|
|
public static OpResult ReplaceOver(string replaced, string replacement, uint flags) {
|
|
OpResult result = new OpResult();
|
|
bool ok = ReplaceFileW(replaced, replacement, null, flags, IntPtr.Zero, IntPtr.Zero);
|
|
int err = Marshal.GetLastWin32Error();
|
|
result.Success = ok;
|
|
result.LastError = ok ? 0 : err;
|
|
result.ErrorName = ErrorName(ok ? 0 : err);
|
|
result.Message = ok ? "" : SystemMessage(err);
|
|
result.Detail = "ReplaceFileW flags=0x" + flags.ToString("X");
|
|
return result;
|
|
}
|
|
|
|
public static RemoteProtocolResult QueryLocality(string path, string driveRoot, bool isDirectory) {
|
|
RemoteProtocolResult result = new RemoteProtocolResult();
|
|
result.Target = path;
|
|
result.InfoClass = FileRemoteProtocolInfo;
|
|
result.DriveType = (int)GetDriveTypeW(driveRoot);
|
|
result.DriveTypeName = DriveTypeName((uint)result.DriveType);
|
|
result.PathIsNetworkPath = PathIsNetworkPathW(path);
|
|
uint flags = isDirectory ? FILE_FLAG_BACKUP_SEMANTICS : 0;
|
|
IntPtr h = CreateFileW(path, 0x80000000, 0x00000007, IntPtr.Zero, OPEN_EXISTING, flags, IntPtr.Zero);
|
|
int openErr = Marshal.GetLastWin32Error();
|
|
if (h == InvalidHandle) {
|
|
result.OpenSuccess = false;
|
|
result.OpenError = openErr;
|
|
result.OpenErrorName = ErrorName(openErr);
|
|
result.Message = SystemMessage(openErr);
|
|
return result;
|
|
}
|
|
result.OpenSuccess = true;
|
|
result.OpenError = 0;
|
|
result.OpenErrorName = ErrorName(0);
|
|
IntPtr buffer = Marshal.AllocHGlobal(512);
|
|
try {
|
|
for (int i = 0; i < 512; i++) { Marshal.WriteByte(buffer, i, 0); }
|
|
bool controlOk = GetFileInformationByHandleEx(h, FileBasicInfo, buffer, 40);
|
|
int controlErr = Marshal.GetLastWin32Error();
|
|
result.ControlCallSuccess = controlOk;
|
|
result.ControlLastError = controlOk ? 0 : controlErr;
|
|
result.ControlErrorName = ErrorName(controlOk ? 0 : controlErr);
|
|
result.ControlDetail = controlOk
|
|
? ("FileBasicInfo.FileAttributes=0x" + Marshal.ReadInt32(buffer, 32).ToString("X"))
|
|
: SystemMessage(controlErr);
|
|
|
|
for (int i = 0; i < 512; i++) { Marshal.WriteByte(buffer, i, 0); }
|
|
bool ok = GetFileInformationByHandleEx(h, FileRemoteProtocolInfo, buffer, 116);
|
|
int err = Marshal.GetLastWin32Error();
|
|
result.CallSuccess = ok;
|
|
result.LastError = ok ? 0 : err;
|
|
result.ErrorName = ErrorName(ok ? 0 : err);
|
|
result.Message = ok ? "" : SystemMessage(err);
|
|
if (ok) {
|
|
result.StructureVersion = (ushort)Marshal.ReadInt16(buffer, 0);
|
|
result.StructureSize = (ushort)Marshal.ReadInt16(buffer, 2);
|
|
uint protocol = (uint)Marshal.ReadInt32(buffer, 4);
|
|
result.Protocol = "0x" + protocol.ToString("X8");
|
|
result.ProtocolName = ProtocolName(protocol);
|
|
result.ProtocolMajorVersion = (ushort)Marshal.ReadInt16(buffer, 8);
|
|
result.ProtocolMinorVersion = (ushort)Marshal.ReadInt16(buffer, 10);
|
|
result.ProtocolRevision = (ushort)Marshal.ReadInt16(buffer, 12);
|
|
uint flagBits = (uint)Marshal.ReadInt32(buffer, 16);
|
|
result.Flags = "0x" + flagBits.ToString("X8");
|
|
result.FlagNames = FlagNames(flagBits);
|
|
}
|
|
|
|
for (int i = 0; i < 512; i++) { Marshal.WriteByte(buffer, i, 0); }
|
|
bool outOfRangeOk = GetFileInformationByHandleEx(h, OutOfRangeInfoClass, buffer, 116);
|
|
int outOfRangeErr = Marshal.GetLastWin32Error();
|
|
result.OutOfRangeClassCallSuccess = outOfRangeOk;
|
|
result.OutOfRangeClassLastError = outOfRangeOk ? 0 : outOfRangeErr;
|
|
result.OutOfRangeClassErrorName = ErrorName(outOfRangeOk ? 0 : outOfRangeErr);
|
|
} finally {
|
|
Marshal.FreeHGlobal(buffer);
|
|
CloseHandle(h);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|