Skip to content

Commit

Permalink
CreateProcess Argument redirection feature (#220) (#222)
Browse files Browse the repository at this point in the history
* CreateProcess Argument redirection feature

* Incorporating review commments

* Incorporating review comments

* Incorporating review comments

* Incorporating review comments
  • Loading branch information
npuvvada committed Dec 30, 2022
1 parent 536201b commit 0f35538
Show file tree
Hide file tree
Showing 19 changed files with 672 additions and 4 deletions.
110 changes: 110 additions & 0 deletions PsfRuntime/ArgRedirection.h
@@ -0,0 +1,110 @@
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include <known_folders.h>

constexpr int MAX_CMDLINE_PATH = 32767;
auto GetFileAttributesImpl = psf::detoured_string_function(&::GetFileAttributesA, &::GetFileAttributesW);

template <typename CharT>
bool IsUnderUserAppDataAndReplace(const CharT* fileName, CharT* cmdLine, bool AppDataLocal)
{
bool isUnderAppData = false;
bool result = false;

if (!fileName)
{
return result;
}
constexpr wchar_t root_local_device_prefix[] = LR"(\\?\)";
constexpr wchar_t root_local_device_prefix_dot[] = LR"(\\.\)";

GUID appDataFolderId = AppDataLocal ? FOLDERID_LocalAppData : FOLDERID_RoamingAppData;

if (std::equal(root_local_device_prefix, root_local_device_prefix + wcslen(root_local_device_prefix), fileName))
{
isUnderAppData = is_path_relative(fileName + wcslen(root_local_device_prefix), psf::known_folder(appDataFolderId));
}
else if (std::equal(root_local_device_prefix_dot, root_local_device_prefix_dot + wcslen(root_local_device_prefix_dot), fileName))
{
isUnderAppData = is_path_relative(fileName + wcslen(root_local_device_prefix_dot), psf::known_folder(appDataFolderId));
}
else
{
isUnderAppData = is_path_relative(fileName, psf::known_folder(appDataFolderId));
}

size_t remBufSize = MAX_CMDLINE_PATH - strlenImpl(cmdLine);
if (isUnderAppData)
{
std::filesystem::path strLocalRoaming = AppDataLocal ? std::filesystem::path(L"Local") : std::filesystem::path(L"Roaming");
std::filesystem::path appDataPath = psf::known_folder(appDataFolderId);
auto packageAppDataPath = psf::known_folder(FOLDERID_LocalAppData) / std::filesystem::path(L"Packages") / psf::current_package_family_name() / LR"(LocalCache)" / strLocalRoaming;
std::wstring relativePath = widen(fileName + (appDataPath.native().length()));
std::wstring fullPath = packageAppDataPath.native() + relativePath;

if (GetFileAttributesImpl(fullPath.c_str()) != INVALID_FILE_ATTRIBUTES)
{
result = true;
if (std::is_same<CharT, char>::value)
{
size_t fullPathLen = fullPath.length();
size_t bytes_written;
std::unique_ptr<char[]> fullPath_char(new char[fullPathLen + 1]);
wcstombs_s(&bytes_written, fullPath_char.get(), fullPathLen + 1, fullPath.c_str(), fullPathLen);
strcatImpl(cmdLine, remBufSize, (const CharT*)(fullPath_char.get()));
}
else
{
strcatImpl(cmdLine, remBufSize, (CharT*)(fullPath.c_str()));
}
}
}
return result;
}

// checks each space separated command line parameter and changes from native local app data folder to per user per app data folder if the file referred in command line parameter is present in per user per app data folder, else it remains the same
template <typename CharT>
void convertCmdLineParameters(CharT* inputCmdLine, CharT* cnvtCmdLine)
{
CharT* next_token;
size_t cmdLinelen = strlenImpl(inputCmdLine);
std::unique_ptr<CharT[]> cmdLine(new CharT[cmdLinelen+1]);
if (cmdLine.get())
{
memset(cmdLine.get(), 0, cmdLinelen+1);
strcatImpl(cmdLine.get(), cmdLinelen+1, inputCmdLine);
}
else
{
strcatImpl(cnvtCmdLine, MAX_CMDLINE_PATH, inputCmdLine);
return;
}

CharT* token = strtokImpl(cmdLine.get(), (CharT*)L" ", &next_token);
bool redirectResult = false;
size_t remBufSize;

while (token != nullptr)
{
redirectResult = IsUnderUserAppDataAndReplace(token, cnvtCmdLine, true);
if (redirectResult == false)
{
redirectResult = IsUnderUserAppDataAndReplace(token, cnvtCmdLine, false);
}

if (!redirectResult)
{
remBufSize = MAX_CMDLINE_PATH - strlenImpl(cnvtCmdLine);
strcatImpl(cnvtCmdLine, remBufSize, token);
}
token = strtokImpl((CharT*)nullptr, (CharT*)L" ", &next_token);
if (token != nullptr)
{
remBufSize = MAX_CMDLINE_PATH - strlenImpl(cnvtCmdLine);
strcatImpl(cnvtCmdLine, remBufSize, (CharT*)L" ");
}
}
return;
}
18 changes: 17 additions & 1 deletion PsfRuntime/CreateProcessHook.cpp
Expand Up @@ -24,6 +24,7 @@
#include <psf_framework.h>

#include "Config.h"
#include "ArgRedirection.h"

using namespace std::literals;

Expand Down Expand Up @@ -153,9 +154,24 @@ BOOL WINAPI CreateProcessFixup(
processInformation = &pi;
}

DWORD cnvtCmdLinePathLen = 0;
if (commandLine)
{
size_t cmdLineLen = strlenImpl(reinterpret_cast<const CharT*>(commandLine));
cnvtCmdLinePathLen = (cmdLineLen ? MAX_CMDLINE_PATH : 0); // avoid allocation when no cmdLine is passed
}

std::unique_ptr<CharT[]> cnvtCmdLine(new CharT[cnvtCmdLinePathLen]);
if (commandLine && cnvtCmdLine.get())
{
// Redirect createProcess arguments if any in native app data to per user per app data
memset(cnvtCmdLine.get(), 0, MAX_CMDLINE_PATH);
convertCmdLineParameters(commandLine, cnvtCmdLine.get());
}

if (!CreateProcessImpl(
applicationName,
commandLine,
(commandLine && cnvtCmdLine.get()) ? cnvtCmdLine.get() : commandLine,
processAttributes,
threadAttributes,
inheritHandles,
Expand Down
1 change: 1 addition & 0 deletions PsfRuntime/PsfRuntime.vcxproj
Expand Up @@ -29,6 +29,7 @@
<ItemGroup>
<ClInclude Include="Config.h" />
<ClInclude Include="JsonConfig.h" />
<ClInclude Include="ArgRedirection.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Detours\Detours.vcxproj">
Expand Down
6 changes: 5 additions & 1 deletion PsfRuntime/PsfRuntime.vcxproj.filters
Expand Up @@ -29,5 +29,9 @@
<ClInclude Include="JsonConfig.h">
<Filter>inc</Filter>
</ClInclude>
<ClInclude Include="ArgRedirection.h">
<Filter>inc</Filter>
</ClInclude>
</ItemGroup>
</Project>
</Project>

46 changes: 46 additions & 0 deletions include/utilities.h
Expand Up @@ -11,6 +11,7 @@
#include <string_view>

#include "win32_error.h"
#include "known_folders.h"

template <typename CharT>
struct case_insensitive_char_traits : std::char_traits<CharT>
Expand Down Expand Up @@ -223,3 +224,48 @@ inline wide_argument_string widen_argument(const wchar_t* str) noexcept
{
return wide_argument_string{ str };
}

template <typename CharT>
errno_t strcatImpl(CharT* dest, rsize_t destBufSize, CharT const* src)
{
if (std::is_same<CharT, char>::value)
{
return strcat_s(reinterpret_cast<char*>(dest), destBufSize, reinterpret_cast<const char*>(src));
}
else
{
return wcscat_s(reinterpret_cast<wchar_t*>(dest), destBufSize, reinterpret_cast<const wchar_t*>(src));
}
}

template <typename CharT>
CharT* strtokImpl(CharT* inpStr, CharT const* delim, CharT** token)
{
if (std::is_same<CharT, char>::value)
{
return reinterpret_cast<CharT*>(strtok_s(reinterpret_cast<char*>(inpStr), reinterpret_cast<const char*>(delim), reinterpret_cast<char**>(token)));
}
else
{
return reinterpret_cast<CharT*>(wcstok_s(reinterpret_cast<wchar_t*>(inpStr), reinterpret_cast<const wchar_t*>(delim), reinterpret_cast<wchar_t**>(token)));
}
}

template <typename CharT>
size_t strlenImpl(CharT const* inpStr)
{
if (std::is_same<CharT, char>::value)
{
return strlen(reinterpret_cast<const char*>(inpStr));
}
else
{
return wcslen(reinterpret_cast<const wchar_t*>(inpStr));
}
}

template <typename CharT>
bool is_path_relative(const CharT* path, const std::filesystem::path& basePath)
{
return std::equal(basePath.native().begin(), basePath.native().end(), path, psf::path_compare{});
}
2 changes: 1 addition & 1 deletion tests/RunTests.ps1
Expand Up @@ -40,7 +40,7 @@ function RunTest($Arch, $Config)
{
# Uninstall all packages on exit. Ideally Add-AppxPackage would give us back something that we could use here,
# but alas we must hard-code it
$packagesToUninstall = @("ArchitectureTest", "CompositionTest", "FileSystemTest", "LongPathsTest", "WorkingDirectoryTest", "PowershellScriptTest", "DynamicLibraryTest", "RegLegacyTest", "EnvVarsATest", "EnvVarsWTest")
$packagesToUninstall = @("ArchitectureTest", "CompositionTest", "FileSystemTest", "LongPathsTest", "WorkingDirectoryTest", "PowershellScriptTest", "DynamicLibraryTest", "RegLegacyTest", "EnvVarsATest", "EnvVarsWTest", "ArgRedirectionTest")
foreach ($pkg in $packagesToUninstall)
{
Get-AppxPackage $pkg | Remove-AppxPackage
Expand Down
4 changes: 3 additions & 1 deletion tests/TestRunner/main.cpp
Expand Up @@ -42,7 +42,9 @@ static constexpr const wchar_t* g_applications[] =
L"EnvVarsATest_8wekyb3d8bbwe!Fixed32",
L"EnvVarsATest_8wekyb3d8bbwe!Fixed64",
L"EnvVarsWTest_8wekyb3d8bbwe!Fixed32",
L"EnvVarsWTest_8wekyb3d8bbwe!Fixed64"
L"EnvVarsWTest_8wekyb3d8bbwe!Fixed64",
L"ArgRedirectionTest_8wekyb3d8bbwe!Fixed32",
L"ArgRedirectionTest_8wekyb3d8bbwe!Fixed64"
};

bool g_onlyPrintSummary = false;
Expand Down
57 changes: 57 additions & 0 deletions tests/scenarios/ArgRedirectionTest/AppxManifest.xml
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10">
<Identity Name="ArgRedirectionTest"
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
Version="0.0.0.1"
ProcessorArchitecture="x64" />
<Properties>
<DisplayName>ArgRedirection Test</DisplayName>
<PublisherDisplayName>Reserved</PublisherDisplayName>
<Description>No description entered</Description>
<Logo>Assets\Logo44x44.png</Logo>
</Properties>
<Resources>
<Resource Language="en-us" />
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14257.0" MaxVersionTested="10.0.14257.0" />
</Dependencies>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
<Applications>
<Application Id="UnFixed64" Executable="ArgRedirectionTest.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements BackgroundColor="transparent"
DisplayName="ArgRedirection Test (Un-Fixed x64)"
Square150x150Logo="Assets\Logo150x150.png"
Square44x44Logo="Assets\Logo44x44.png"
Description="No description entered" />
</Application>
<Application Id="UnFixed32" Executable="ArgRedirectionTest.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements BackgroundColor="transparent"
DisplayName="ArgRedirection Test (Un-Fixed x86)"
Square150x150Logo="Assets\Logo150x150.png"
Square44x44Logo="Assets\Logo44x44.png"
Description="No description entered" />
</Application>
<Application Id="Fixed64" Executable="PsfLauncher.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements BackgroundColor="transparent"
DisplayName="ArgRedirection Test (Un-Fixed x64)"
Square150x150Logo="Assets\Logo150x150.png"
Square44x44Logo="Assets\Logo44x44.png"
Description="No description entered" />
</Application>
<Application Id="Fixed32" Executable="PsfLauncher.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements BackgroundColor="transparent"
DisplayName="ArgRedirection Test (Un-Fixed x86)"
Square150x150Logo="Assets\Logo150x150.png"
Square44x44Logo="Assets\Logo44x44.png"
Description="No description entered" />
</Application>
</Applications>
</Package>

0 comments on commit 0f35538

Please sign in to comment.