Compare commits

...

7 Commits

Author SHA1 Message Date
Mengyuan Chen (from Dev Box)
d1b4fee0b6 add annotations and change net7.0--net8.0 2025-02-24 20:59:02 +08:00
Mengyuan Chen (from Dev Box)
6ee7dba97e merge main into code 2025-02-24 20:41:27 +08:00
Mengyuan Chen (from Dev Box)
330e292037 add registrypreview fuzzing code 2025-02-24 20:35:30 +08:00
Mengyuan Chen (from Dev Box)
5e28035d9f update code 2025-02-21 19:31:19 +08:00
Mengyuan Chen (from Dev Box)
88f5e65ee8 add fuzz tests framework in registrypreview 2025-02-11 10:53:24 +08:00
Mengyuan Chen (from Dev Box)
8aefd45805 Merge remote-tracking branch 'origin/main' into dev/mengyuanchen/add_onefuzz/registrypreview 2025-02-10 17:00:56 +08:00
Mengyuan Chen (from Dev Box)
b205794d6a Add fuzz test cases 2025-01-28 22:00:17 +08:00
8 changed files with 444 additions and 14 deletions

View File

@@ -26,7 +26,7 @@ jobs:
displayName: Download artifacts
artifact: $(ArtifactName)
patterns: |-
**/tests/*.FuzzTests/**
**/tests/RegistryPreview.FuzzTests/**
- task: onefuzz-task@0
inputs:

View File

@@ -646,6 +646,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hosts.FuzzTests", "src\modu
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hosts.UITests", "src\modules\Hosts\Hosts.UITests\Hosts.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RegistryPreview.FuzzTests", "src\modules\registrypreview\RegistryPreview.FuzzTests\RegistryPreview.FuzzTests.csproj", "{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2294,6 +2296,14 @@ Global
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|ARM64.Build.0 = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|x64.ActiveCfg = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|x64.Build.0 = Release|x64
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Debug|ARM64.ActiveCfg = Debug|ARM64
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Debug|ARM64.Build.0 = Debug|ARM64
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Debug|x64.ActiveCfg = Debug|x64
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Debug|x64.Build.0 = Debug|x64
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Release|ARM64.ActiveCfg = Release|ARM64
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Release|ARM64.Build.0 = Release|ARM64
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Release|x64.ActiveCfg = Release|x64
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2534,6 +2544,7 @@ Global
{4382A954-179A-4078-92AF-715187DFFF50} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
{EBED240C-8702-452D-B764-6DB9DA9179AF} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@@ -0,0 +1,177 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Globalization;
using System.Resources;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Win32;
using RegistryPreviewUILib;
namespace RegistryPreview.FuzzTests
{
public class FuzzTests
{
private const string REGISTRYHEADER4 = "regedit4";
private const string REGISTRYHEADER5 = "windows registry editor version 5.00";
private const string KEYIMAGE = "ms-appx:///Assets/RegistryPreview/folder32.png";
private const string DELETEDKEYIMAGE = "ms-appx:///Assets/RegistryPreview/deleted-folder32.png";
// Case 1: Fuzz test for CheckKeyLineForBrackets
public static void FuzzCheckKeyLineForBrackets(ReadOnlySpan<byte> input)
{
string registryLine;
// Simulate registry file content as filenameText
var filenameText = GenerateRegistryHeader(input);
string[] registryLines = filenameText.Split("\r");
if (registryLines.Length <= 1)
{
return;
}
// REG files have to start with one of two headers and it's case-insensitive
// The header in the registry file is either REGISTRYHEADER4 or REGISTRYHEADER5
registryLine = registryLines[0];
// Check if the registry header is valid
if (!IsValidRegistryHeader(registryLine))
{
return;
}
int index = 1;
registryLine = registryLines[index]; // Extract content after the header
ParseHelper.ProcessRegistryLine(registryLine);
if (registryLine.StartsWith("[-", StringComparison.InvariantCulture))
{
// remove the - as we won't need it but it will get special treatment in the UI
registryLine = registryLine.Remove(1, 1);
string imageName = DELETEDKEYIMAGE;
// Fuzz test for the CheckKeyLineForBrackets method
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
}
else if (registryLine.StartsWith('['))
{
string imageName = KEYIMAGE;
// Fuzz test for the CheckKeyLineForBrackets method
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
}
else
{
return;
}
}
// Case 1: Fuzz test for StripFirstAndLast
public static void FuzzStripFirstAndLast(ReadOnlySpan<byte> input)
{
string registryLine;
var filenameText = GenerateRegistryHeader(input);
filenameText = filenameText.Replace("\r\n", "\r");
string[] registryLines = filenameText.Split("\r");
if (registryLines.Length <= 1)
{
return;
}
// REG files have to start with one of two headers and it's case-insensitive
registryLine = registryLines[0];
if (!IsValidRegistryHeader(registryLine))
{
return;
}
int index = 1;
registryLine = registryLines[index];
ParseHelper.ProcessRegistryLine(registryLine);
if (registryLine.StartsWith("[-", StringComparison.InvariantCulture))
{
// remove the - as we won't need it but it will get special treatment in the UI
registryLine = registryLine.Remove(1, 1);
string imageName = DELETEDKEYIMAGE;
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
// Fuzz test for the StripFirstAndLast method
registryLine = ParseHelper.StripFirstAndLast(registryLine);
}
else if (registryLine.StartsWith('['))
{
string imageName = KEYIMAGE;
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
// Fuzz test for the StripFirstAndLast method
registryLine = ParseHelper.StripFirstAndLast(registryLine);
}
else if (registryLine.StartsWith('"') && registryLine.EndsWith("=-", StringComparison.InvariantCulture))
{
registryLine = registryLine.Replace("=-", string.Empty);
// remove the "'s without removing all of them
// Fuzz test for the StripFirstAndLast method
registryLine = ParseHelper.StripFirstAndLast(registryLine);
}
else if (registryLine.StartsWith('"'))
{
int equal = registryLine.IndexOf('=');
if ((equal < 0) || (equal > registryLine.Length - 1))
{
// something is very wrong
return;
}
// set the name and the value
string name = registryLine.Substring(0, equal);
// trim the whitespace and quotes from the name
name = name.Trim();
// Fuzz test for the StripFirstAndLast method
name = ParseHelper.StripFirstAndLast(name);
}
else
{
return;
}
}
public static string GenerateRegistryHeader(ReadOnlySpan<byte> input)
{
string header = new Random().Next(2) == 0 ? REGISTRYHEADER4 : REGISTRYHEADER5;
string inputText = System.Text.Encoding.UTF8.GetString(input);
string filenameText = header + "\r\n" + inputText;
return filenameText.Replace("\r\n", "\r");
}
private static bool IsValidRegistryHeader(string line)
{
// Convert the line to lowercase once for comparison
var lineLower = line.ToLowerInvariant();
switch (line)
{
case REGISTRYHEADER4:
case REGISTRYHEADER5:
return true;
default:
return false;
}
}
}
}

View File

@@ -0,0 +1,5 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]

View File

@@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
{
"configVersion": 3,
"entries": [
{
"fuzzer": {
"$type": "libfuzzerDotNet",
"dll": "RegistryPreview.FuzzTests.dll",
"class": "RegistryPreview.FuzzTests.FuzzTests",
"method": "FuzzCheckKeyLineForBrackets",
"FuzzingTargetBinaries": [
"PowerToys.RegistryPreview.dll"
]
},
"adoTemplate": {
// supply the values appropriate to your
// project, where bugs will be filed
"org": "microsoft",
"project": "OS",
"AssignedTo": "mengyuanchen@microsoft.com",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
"IterationPath": "OS\\Future"
},
"jobNotificationEmail": "mengyuanchen@microsoft.com",
"skip": false,
"rebootAfterSetup": false,
"oneFuzzJobs": [
// at least one job is required
{
"projectName": "RegistryPreview",
"targetName": "RegistryPreview-dotnet-CheckKeyLineForBrackets-fuzzer"
}
],
"jobDependencies": [
// this should contain, at minimum,
// the DLL and PDB files
// you will need to add any other files required
// (globs are supported)
"RegistryPreview.FuzzTests.dll",
"RegistryPreview.FuzzTests.pdb",
"Microsoft.Windows.SDK.NET.dll",
"WinRT.Runtime.dll"
],
"SdlWorkItemId": 49911822
},
{
"fuzzer": {
"$type": "libfuzzerDotNet",
"dll": "RegistryPreview.FuzzTests.dll",
"class": "RegistryPreview.FuzzTests.FuzzTests",
"method": "FuzzStripFirstAndLast",
"FuzzingTargetBinaries": [
"PowerToys.RegistryPreview.dll"
]
},
"adoTemplate": {
// supply the values appropriate to your
// project, where bugs will be filed
"org": "microsoft",
"project": "OS",
"AssignedTo": "mengyuanchen@microsoft.com",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
"IterationPath": "OS\\Future"
},
"jobNotificationEmail": "mengyuanchen@microsoft.com",
"skip": false,
"rebootAfterSetup": false,
"oneFuzzJobs": [
// at least one job is required
{
"projectName": "RegistryPreview",
"targetName": "RegistryPreview-dotnet-StripFirstAndLasts-fuzzer"
}
],
"jobDependencies": [
// this should contain, at minimum,
// the DLL and PDB files
// you will need to add any other files required
// (globs are supported)
"RegistryPreview.FuzzTests.dll",
"RegistryPreview.FuzzTests.pdb",
"Microsoft.Windows.SDK.NET.dll",
"WinRT.Runtime.dll"
],
"SdlWorkItemId": 49911822
}
]
}

View File

@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<Platforms>x64;ARM64</Platforms>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\RegistryPreview.FuzzTests\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\RegistryPreviewUILib\ParseHelper.cs" Link="ParseHelper.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="OneFuzzConfig.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="MSTest" />
</ItemGroup>
<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RegistryPreviewUILib
{
public class ParseHelper
{
private const string ERRORIMAGE = "ms-appx:///Assets/RegistryPreview/error32.png";
/// <summary>
/// Checks a Key line for the closing bracket and treat it as an error if it cannot be found
/// </summary>
public static void CheckKeyLineForBrackets(ref string registryLine, ref string imageName)
{
// following the current behavior of the registry editor, find the last ] and treat everything else as ignorable
int lastBracket = registryLine.LastIndexOf(']');
if (lastBracket == -1)
{
// since we don't have a last bracket yet, add an extra space and continue processing
registryLine += " ";
imageName = ERRORIMAGE;
}
else
{
// having found the last ] and there is text after it, drop the rest of the string on the floor
if (lastBracket < registryLine.Length - 1)
{
registryLine = registryLine.Substring(0, lastBracket + 1);
}
if (CheckForKnownGoodBranches(registryLine) == false)
{
imageName = ERRORIMAGE;
}
}
}
/// <summary>
/// Make sure the root of a full path start with one of the five "hard coded" roots. Throw an error for the branch if it doesn't.
/// </summary>
private static bool CheckForKnownGoodBranches(string key)
{
if ((key.StartsWith("[HKEY_CLASSES_ROOT]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKEY_CURRENT_USER]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKEY_USERS]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKEY_LOCAL_MACHINE]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKEY_CURRENT_CONFIG]", StringComparison.InvariantCultureIgnoreCase) == false)
&&
(key.StartsWith(@"[HKEY_CLASSES_ROOT\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKEY_CURRENT_USER\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKEY_USERS\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKEY_LOCAL_MACHINE\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKEY_CURRENT_CONFIG\", StringComparison.InvariantCultureIgnoreCase) == false)
&&
(key.StartsWith("[HKCR]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKCU]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKU]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKLM]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKCC]", StringComparison.InvariantCultureIgnoreCase) == false)
&&
(key.StartsWith(@"[HKCR\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKCU\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKU\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKLM\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKCC\", StringComparison.InvariantCultureIgnoreCase) == false))
{
return false;
}
return true;
}
/// <summary>
/// Rip the first and last character off a string,
/// checking that the string is at least 2 characters long to avoid errors
/// </summary>
public static string StripFirstAndLast(string line)
{
if (line.Length > 1)
{
line = line.Remove(line.Length - 1, 1);
line = line.Remove(0, 1);
}
return line;
}
public static string ProcessRegistryLine(string registryLine)
{
if (registryLine.StartsWith("@=-", StringComparison.InvariantCulture))
{
// REG file has a callout to delete the @ Value which won't work *but* the Registry Editor will
// clear the value of the @ Value instead, so it's still a valid line.
registryLine = registryLine.Replace("@=-", "\"(Default)\"=\"\"");
}
else if (registryLine.StartsWith("@=", StringComparison.InvariantCulture))
{
// This is the Value called "(Default)" so we tweak the line for the UX
registryLine = registryLine.Replace("@=", "\"(Default)\"=");
}
return registryLine;
}
}
}

View File

@@ -225,7 +225,9 @@ namespace RegistryPreviewUILib
{
// special case for when the registryLine begins with a @ - make some tweaks and
// let the regular processing handle the rest.
if (registryLine.StartsWith("@=-", StringComparison.InvariantCulture))
registryLine = ParseHelper.ProcessRegistryLine(registryLine);
/* if (registryLine.StartsWith("@=-", StringComparison.InvariantCulture))
{
// REG file has a callout to delete the @ Value which won't work *but* the Registry Editor will
// clear the value of the @ Value instead, so it's still a valid line.
@@ -235,7 +237,7 @@ namespace RegistryPreviewUILib
{
// This is the Value called "(Default)" so we tweak the line for the UX
registryLine = registryLine.Replace("@=", "\"(Default)\"=");
}
}*/
// continue until we have nothing left to read
// switch logic, based off what the current line we're reading is
@@ -245,10 +247,10 @@ namespace RegistryPreviewUILib
registryLine = registryLine.Remove(1, 1);
string imageName = DELETEDKEYIMAGE;
CheckKeyLineForBrackets(ref registryLine, ref imageName);
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
// this is a key, so remove the first [ and last ]
registryLine = StripFirstAndLast(registryLine);
registryLine = ParseHelper.StripFirstAndLast(registryLine);
// do not track the result of this node, since it should have no children
AddTextToTree(registryLine, imageName);
@@ -256,10 +258,10 @@ namespace RegistryPreviewUILib
else if (registryLine.StartsWith('['))
{
string imageName = KEYIMAGE;
CheckKeyLineForBrackets(ref registryLine, ref imageName);
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
// this is a key, so remove the first [ and last ]
registryLine = StripFirstAndLast(registryLine);
registryLine = ParseHelper.StripFirstAndLast(registryLine);
treeViewNode = AddTextToTree(registryLine, imageName);
lastKeyPath = registryLine;
@@ -270,7 +272,7 @@ namespace RegistryPreviewUILib
registryLine = registryLine.Replace("=-", string.Empty);
// remove the "'s without removing all of them
registryLine = StripFirstAndLast(registryLine);
registryLine = ParseHelper.StripFirstAndLast(registryLine);
// Create a new listview item that will be used to display the delete value and store it
registryValue = new RegistryValue(registryLine, string.Empty, string.Empty, lastKeyPath);
@@ -300,7 +302,7 @@ namespace RegistryPreviewUILib
// trim the whitespace and quotes from the name
name = name.Trim();
name = StripFirstAndLast(name);
name = ParseHelper.StripFirstAndLast(name);
// Clean out any escaped characters in the value, only for the preview
name = StripEscapedCharacters(name);
@@ -326,7 +328,7 @@ namespace RegistryPreviewUILib
if (value.StartsWith('"') && value.EndsWith('"'))
{
value = StripFirstAndLast(value);
value = ParseHelper.StripFirstAndLast(value);
}
else
{
@@ -1038,7 +1040,7 @@ namespace RegistryPreviewUILib
/// <summary>
/// Checks a Key line for the closing bracket and treat it as an error if it cannot be found
/// </summary>
private void CheckKeyLineForBrackets(ref string registryLine, ref string imageName)
/* private void CheckKeyLineForBrackets(ref string registryLine, ref string imageName)
{
// following the current behavior of the registry editor, find the last ] and treat everything else as ignorable
int lastBracket = registryLine.LastIndexOf(']');
@@ -1061,7 +1063,7 @@ namespace RegistryPreviewUILib
imageName = ERRORIMAGE;
}
}
}
} */
/// <summary>
/// Takes a binary registry value, sees if it has a ; and dumps the rest of the line - this does not work for REG_SZ values
@@ -1082,7 +1084,7 @@ namespace RegistryPreviewUILib
/// <summary>
/// Make sure the root of a full path start with one of the five "hard coded" roots. Throw an error for the branch if it doesn't.
/// </summary>
private bool CheckForKnownGoodBranches(string key)
/* private bool CheckForKnownGoodBranches(string key)
{
if ((key.StartsWith("[HKEY_CLASSES_ROOT]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKEY_CURRENT_USER]", StringComparison.InvariantCultureIgnoreCase) == false &&
@@ -1112,7 +1114,7 @@ namespace RegistryPreviewUILib
}
return true;
}
} */
/// <summary>
/// Turns the Open Key button in the command bar on/off, depending on if a key is selected