Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace WMI-powered filesystem compression #292

Draft
wants to merge 52 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
9edb6e2
refactor: add FSCrompression, uses PInvoke
BinToss Feb 21, 2022
a56dd63
refactor: make FSCompression static
BinToss Feb 21, 2022
3c89746
refactor: rename FileSystemCompression
BinToss Feb 21, 2022
8684200
build: AllowUnsafeBlocks
BinToss Mar 14, 2022
ada6dee
build: add PackageReference pinvoke.kernel32
BinToss Mar 14, 2022
0702827
refactor: overhaul FileSystemCompression class
BinToss Mar 14, 2022
a40015a
build: re-add Fody, Costura.Fody
BinToss Mar 21, 2022
836e92f
build(deps): fix name of PInvoke.Kernel32
BinToss Mar 21, 2022
743e558
refactor: change DirectoyInfo.Compress()'s progress from Int to HXE.S…
BinToss Mar 21, 2022
2924353
fix: increment Compress()'s status properly
BinToss Mar 21, 2022
4fffd4d
fix: replace enableLZNT1 procedure
BinToss Mar 21, 2022
97592cd
feat: add --applyLznt1= CLI parameter
BinToss Mar 21, 2022
a52c8c1
build(deps): add CsWin32 and PInvoke.AdvApi32
BinToss Apr 10, 2022
e9beeab
feat: add Process extensions
BinToss Apr 10, 2022
4518e39
build: fix condition of EnableCompressionInSingleFile
BinToss Apr 10, 2022
9848513
build: update condition of RuntimeIdentifier
BinToss Apr 10, 2022
37c556a
chore(vscode): update VSCode/IDE launch configs
BinToss Apr 10, 2022
fc5c4d7
docs: add copyright header to FileSystemCompression
BinToss Apr 10, 2022
a7ec42f
fix: get FileSystemCompression working
BinToss Apr 10, 2022
4e01754
refactor: move equivalent of Get-ChildItems to new method and struct
BinToss Apr 17, 2022
42dfde9
refactor: move ThrowWin32Exception to new InfoWin32Exception class
BinToss Apr 17, 2022
1e96335
refactor: make Process.IsElevated() throw NotSupportedException for S…
BinToss Apr 17, 2022
1d1f6d2
chore: add TODO for Process.Security member
BinToss Apr 17, 2022
6defbbf
chore: add comments and inline docs
BinToss Apr 17, 2022
bd72212
refactor: initialize Status with Total 0
BinToss Apr 17, 2022
b6e5f2b
refactor: add exception-throwing wrapper of CreateFile
BinToss Apr 17, 2022
5cf8904
refactor: replace IsElevated's NotSupportedException with InvalidOper…
BinToss May 27, 2023
7fadb34
fix: call GetLastPInvokeError instead of GetLastWin32Error
BinToss May 27, 2023
27260ec
build: set TargetFramework back to net6.0
BinToss May 27, 2023
6c53853
docs: make custom 'TokenAccessMask' inheritdoc as much as possible
BinToss May 27, 2023
58334c8
fix: fix reference to TokenAccessMask.Query
BinToss May 27, 2023
aae7666
build: set TargetFramework back to net6.0
BinToss May 27, 2023
471e74d
build: switch to net6.0-windows until we can get rid of WPF
BinToss May 27, 2023
2e9b309
build: use latest C# language version
BinToss May 27, 2023
40f48e2
build: remove redundant property 'Deterministic'
BinToss May 27, 2023
946bbff
revert(deps): "build: re-add Fody, Costura.Fody"
BinToss May 27, 2023
4c74ded
build(deps): update dependency microsoft.windows.cswin32 to v0.2.252-…
BinToss May 27, 2023
52126a8
build(deps): update PInvoke monorepo to v0.7.124
BinToss May 27, 2023
579dfc0
Merge branch 'updateBuildConfig' into hotfix/fscompression
BinToss May 27, 2023
1cfa07a
chore(vscode): update launch configs to net6.0-windows/win7-x86
BinToss May 27, 2023
4b331d8
chore(vscode): restrict launch config 'Debug LZNT1' to operate only …
BinToss May 27, 2023
b479c92
Merge branch 'updateVscodeConfig' into hotfix/fscompression
BinToss May 27, 2023
b7695b1
refactor: wrap some specific SET_COMPRESSION errors
BinToss May 27, 2023
d0cb357
fix: return GetHandle's FileStream to dispose it later
BinToss May 27, 2023
f856cc9
docs: update SetSeBackupPrivilege documentation
BinToss May 27, 2023
489a11d
build(deps): re-add CsWin32's PrivateAssets property
BinToss May 27, 2023
04e1953
build: enable prop EmitCompilerGeneratedFiles
BinToss May 27, 2023
4045c34
build: comment out prop TargetFrameworks
BinToss May 27, 2023
07c58c6
fix: update PInvoke, CsWin32 references
BinToss May 27, 2023
abca804
feat: add SharingViolationException
BinToss May 28, 2023
9486b41
refactor: improve GetHandle exception messages
BinToss May 28, 2023
4f07f2f
refactor: change SubItems to class
BinToss May 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,28 @@
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder/bin/Debug/net462/HXE.dll",
"program": "${workspaceFolder}/bin/Debug/net6.0-windows/win7-x86/HXE.dll",
"args": [],
"cwd": "${workspaceFolder}/src",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": "Debug LZNT1 - .NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/bin/Debug/net6.0-windows/win7-x86/HXE.exe",
"args": [
"--applyLznt1='${workspaceFolder}/README.md'"
],
"cwd": "${workspaceFolder}/src",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
Expand Down
329 changes: 329 additions & 0 deletions src/Common/FileSystemCompression.cs

Large diffs are not rendered by default.

62 changes: 62 additions & 0 deletions src/Exceptions/SharingViolationException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Copyright (c) 2023 Noah Sherwin
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
using System;
using System.Runtime.Serialization;
using PInvoke;
using Windows.Win32.Foundation;

namespace HXE.Exceptions;

[Serializable]
internal class SharingViolationException : System.IO.IOException
{
public SharingViolationException()
{ }

public SharingViolationException(string message) : base(message)
{ }

public SharingViolationException(string message, Exception innerException) : base(message, innerException)
{ }

protected SharingViolationException(SerializationInfo info, StreamingContext context) : base(info, context)
{ }

public SharingViolationException(string message, int hresult) : base(message, hresult)
{ }

public static SharingViolationException FromWin32ErrorCode(Win32ErrorCode win32ErrorCode)
{
if (win32ErrorCode is not Win32ErrorCode.ERROR_SHARING_VIOLATION)
throw new ArgumentException($"This method can only be used with {Win32ErrorCode.ERROR_SHARING_VIOLATION}.", paramName: nameof(win32ErrorCode));

var win32Exception = new Win32Exception(win32ErrorCode);
return new SharingViolationException(win32Exception.Message, win32Exception);
}

public static SharingViolationException FromWin32ErrorCode(WIN32_ERROR win32ErrorCode)
{
if (win32ErrorCode is not WIN32_ERROR.ERROR_SHARING_VIOLATION)
throw new ArgumentException($"This method can only be used with {Win32ErrorCode.ERROR_SHARING_VIOLATION}.", paramName: nameof(win32ErrorCode));

var win32Exception = new Win32Exception((Win32ErrorCode)win32ErrorCode);
return new SharingViolationException(win32Exception.Message, win32Exception);
}
}
256 changes: 256 additions & 0 deletions src/Extensions/Process.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
using System;
using System.Security.Principal;
using Microsoft.Win32.SafeHandles;
using PInvoke;
using Windows.Win32.Foundation;
using Windows.Win32.Security;
using static Windows.Win32.PInvoke;
using static Windows.Win32.Security.TOKEN_PRIVILEGES_ATTRIBUTES;
using Win32Exception = System.ComponentModel.Win32Exception;

namespace HXE.Extensions
{
// TODO: Process.Security member. See https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights
public static class ExtProcess
{
/// <summary>
/// See <see href="https://docs.microsoft.com/en-us/windows/win32/secauthz/well-known-sids">Well-known SIDs</see>.
/// </summary>
internal const int DOMAIN_GROUP_RID_ADMINS = 0x200;
public static bool IsCurrentProcessElevated()
{
// WindowsPrincipal.IsInRole() secretly checks process security tokens

WindowsIdentity identity = WindowsIdentity.GetCurrent(ifImpersonating: false);
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator)
|| principal.IsInRole(DOMAIN_GROUP_RID_ADMINS);
}

/// <summary>
/// Check if the given process is running with elevated permissions, typically due to being run as Administrator.
/// </summary>
/// <param name="process">The process to inspect for elevated permissions.</param>
/// <returns><see langword="true"/> if the given process is running with elevated permissions. Else, <see langword="false"/>.</returns>
/// <exception cref="InvalidOperationException">This method cannot operate on fake processes such as System (PID 4).</exception>
/// <exception cref="ArgumentNullException"><paramref name="process"/> is <c>null</c>.</exception>
/// <exception cref="Win32Exception">The native, Win32 error encountered when calling the native function OpenProcessToken. More often than not, this is typically ERROR_ACCESS_DENIED due to the current process having insufficient permission to inspect the target process.</exception>
public static bool IsElevated(this System.Diagnostics.Process process)
{
if (process == null)
throw new ArgumentNullException(nameof(process));

if (process.SafeHandle != null && process.Id == 4)
throw new InvalidOperationException("System (PID 4) token can't be opened");

if (!(bool)OpenProcessToken(
ProcessHandle: process.SafeHandle,
DesiredAccess: TOKEN_ACCESS_MASK.TOKEN_QUERY,
TokenHandle: out SafeFileHandle tokenHandle))
{
throw new Win32Exception();
}

TOKEN_ELEVATION_TYPE elevationType;

using (tokenHandle)
using (Kernel32.SafeObjectHandle objectHandle = new Kernel32.SafeObjectHandle(tokenHandle.DangerousGetHandle()))
{
elevationType = (TOKEN_ELEVATION_TYPE)AdvApi32.GetTokenElevationType(objectHandle);
}

return elevationType == TOKEN_ELEVATION_TYPE.TokenElevationTypeFull;
}

/// <summary>
///
/// </summary>
/// <param name="process"></param>
/// <returns></returns>
/// TODO: NOT TESTED!
public static bool HasSeBackupPrivilege(this System.Diagnostics.Process process)
{
///
/// Initialize new TOKEN_PRIVILEGES instance for SE_BACKUP_NAME
///

TOKEN_PRIVILEGES tokenPrivilege = new TOKEN_PRIVILEGES
{
PrivilegeCount = 1
};
tokenPrivilege.Privileges._0.Luid.LowPart = 0u;
tokenPrivilege.Privileges._0.Luid.HighPart = 0;

///
/// Get Process Token
///
SafeFileHandle processToken = GetProcessToken(process, TokenAccessMask.Query);

///
/// Get Locally Unique Idenifier (LUID) of SE_BACKUP_NAME
///

if (!LookupPrivilegeValue(
lpSystemName: null,
lpName: SE_BACKUP_NAME,
lpLuid: out tokenPrivilege.Privileges._0.Luid))
{
throw new Win32Exception();
}

PRIVILEGE_SET requiredPrivileges = new PRIVILEGE_SET
{
PrivilegeCount = 1
};
requiredPrivileges.Privilege._0.Luid.LowPart = 0u;
requiredPrivileges.Privilege._0.Luid.HighPart = 0;

///
/// Check for process token for SE_BACKUP_NAME
///

if (!PrivilegeCheck(
processToken,
ref requiredPrivileges,
out int pfResult
))
{
throw new Win32Exception();
}
return pfResult != 0;
}

[Flags]
public enum TokenAccessMask : uint
{
None = 0,
/// <inheritdoc cref="TokenAccessLevels.AssignPrimary"/>
AssignPrimary = TokenAccessLevels.AssignPrimary,
/// <inheritdoc cref="TokenAccessLevels.Duplicate"/>
Duplicate = TokenAccessLevels.Duplicate,
/// <inheritdoc cref="TokenAccessLevels.Impersonate"/>
Impersonate = TokenAccessLevels.Impersonate,
/// <inheritdoc cref="TokenAccessLevels.Query"/>
Query = TokenAccessLevels.Query,
/// <inheritdoc cref="TokenAccessLevels.QuerySource"/>
QuerySource = TokenAccessLevels.QuerySource,
/// <inheritdoc cref="TokenAccessLevels.AdjustPrivileges"/>
AdjustPrivileges = TokenAccessLevels.AdjustPrivileges,
/// <inheritdoc cref="TokenAccessLevels.AdjustGroups"/>
AdjustGroups = TokenAccessLevels.AdjustGroups,
/// <inheritdoc cref="TokenAccessLevels.AdjustDefault"/>
AdjustDefault = TokenAccessLevels.AdjustDefault,
/// <inheritdoc cref="TokenAccessLevels.AdjustSessionId"/>
AdjustSessionId = TokenAccessLevels.AdjustSessionId,
Delete = TOKEN_ACCESS_MASK.TOKEN_DELETE,
/// <summary>STANDARD_RIGHTS_READ</summary>
ReadControl = TOKEN_ACCESS_MASK.TOKEN_READ_CONTROL,
/// <inheritdoc cref="TokenAccessLevels.Read"/>
Read = Query | ReadControl,
/// <inheritdoc cref="TokenAccessLevels.Write"/>
Write = ReadControl | AdjustPrivileges | AdjustGroups | AdjustDefault,
WriteDac = TOKEN_ACCESS_MASK.TOKEN_WRITE_DAC,
WriteOwner = TOKEN_ACCESS_MASK.TOKEN_WRITE_OWNER,
// STANDARD_RIGHTS_REQUIRED = 0xF0000 // 983040U
/// <inheritdoc cref="TokenAccessLevels.AllAccess"/>
AllAccess = TokenAccessLevels.AllAccess,
AccessSystemSecurity = TOKEN_ACCESS_MASK.TOKEN_ACCESS_SYSTEM_SECURITY,
/// <inheritdoc cref="TokenAccessLevels.MaximumAllowed"/>
MaximumAllowed = TokenAccessLevels.MaximumAllowed
}

/// <summary>
/// Get a Win32 Process security token
/// </summary>
/// <param name="process">The process to get a token from.</param>
/// <returns>A SafeFileHandle representing the process's security token with the given access privileges.</returns>
public static SafeFileHandle GetProcessToken(this System.Diagnostics.Process process, TokenAccessMask access)
{
if (!OpenProcessToken(process.SafeHandle,
(TOKEN_ACCESS_MASK)access,
out SafeFileHandle processToken))
{
//TODO: improve exception handling. Create/Use new exception types
throw new Win32Exception();
}

return processToken;
}

// AdjustTokenPrivileges(
// System.Runtime.InteropServices.SafeHandle TokenHandle,
// BOOL DisableAllPrivileges,
// TOKEN_PRIVILEGES? NewState,
// uint BufferLength,
// TOKEN_PRIVILEGES* PreviousState,
// uint* ReturnLength)
//public static void AdjustTokenPrivileges(this SafeFileHandle processToken){}

/// <summary>
/// Enable or disable SE_BACKUP_PRIVILEGE for the given process. This privilege must be enabled to toggle directories' COMPRESSION attributes and flags.
/// </summary>
/// <param name="process">The process for which SE_BACKUP_PRIVILEGE will be enabled, if the caller has permission to do so.</param>
/// <param name="enable">If <see langword="true"/> (default), SE_BACKUP_PRIVILEGE will be enabled for the given process. Else, the privilege will be disabled for the given process.</param>
/// <exception cref="Win32Exception">LookupPrivilegeValue or AdjustTokenPrivileges failed.</exception>
public static void SetSeBackupPrivilege(this System.Diagnostics.Process process, bool enable = true)
{
SafeFileHandle processToken = process.GetProcessToken(TokenAccessMask.AdjustPrivileges | TokenAccessMask.Query);

if (!LookupPrivilegeValue(null, SE_BACKUP_NAME, out LUID luidPrivilege))
{
throw new Win32Exception();
}

TOKEN_PRIVILEGES privileges;
privileges.PrivilegeCount = 1;
privileges.Privileges._0.Luid = luidPrivilege;
privileges.Privileges._0.Attributes = SE_PRIVILEGE_ENABLED;

unsafe
{
if (!AdjustTokenPrivileges(
processToken,
false,
privileges,
0,
null,
null
))
{
throw new Win32Exception();
}
}
}
/* Get TokenInformation as defined Type
internal static TokenInformation GetTokenInformation(this System.Diagnostics.Process process)
{
if (process == null)
throw new ArgumentNullException(nameof(process));

if (process.SafeHandle != null && process.Id == 4)
{
// TODO: better exception Type
throw new AccessViolationException("System (PID 4) token can't be opened");
}
else
{
bool optSuccess = AdvApi32.OpenProcessToken(
processHandle: process.SafeHandle.DangerousGetHandle(),
desiredAccess: AdvApi32.TokenAccessRights.TOKEN_QUERY | AdvApi32.TokenAccessRights.TOKEN_DUPLICATE,
tokenHandle: out Kernel32.SafeObjectHandle tokenHandle);
if (!optSuccess)
{
Win32ErrorCode error = Kernel32.GetLastError();
throw new PInvoke.Win32Exception(error, message: error.GetMessage());
}
AdvApi32.GetTokenInformation(
TokenHandle: tokenHandle,
TokenInformationClass: AdvApi32.TOKEN_INFORMATION_CLASS.,
TokenInformation: null,
TokenInformationLength: null,
ReturnLength: out int length
);
}
}*/
}
}
Loading