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

[Fuzz] Add Fuzz testing for RegistryPreview #37607

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Changes from 11 commits
Commits
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: 11 additions & 6 deletions PowerToys.sln
Original file line number Diff line number Diff line change
@@ -644,6 +644,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditorLibrar
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hosts.FuzzTests", "src\modules\Hosts\Hosts.FuzzTests\Hosts.FuzzTests.csproj", "{EBED240C-8702-452D-B764-6DB9DA9179AF}"
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
@@ -2252,20 +2254,14 @@ Global
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|ARM64.Build.0 = Release|ARM64
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x64.ActiveCfg = Release|x64
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x64.Build.0 = Release|x64
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x86.ActiveCfg = Release|x64
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x86.Build.0 = Release|x64
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|ARM64.Build.0 = Debug|ARM64
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x64.ActiveCfg = Debug|x64
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x64.Build.0 = Debug|x64
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x86.ActiveCfg = Debug|x64
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x86.Build.0 = Debug|x64
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|ARM64.ActiveCfg = Release|ARM64
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|ARM64.Build.0 = Release|ARM64
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x64.ActiveCfg = Release|x64
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x64.Build.0 = Release|x64
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x86.ActiveCfg = Release|x64
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x86.Build.0 = Release|x64
{08F9155D-B6DC-46E5-9C83-AF60B655898B}.Debug|ARM64.ActiveCfg = Debug|ARM64
{08F9155D-B6DC-46E5-9C83-AF60B655898B}.Debug|ARM64.Build.0 = Debug|ARM64
{08F9155D-B6DC-46E5-9C83-AF60B655898B}.Debug|x64.ActiveCfg = Debug|x64
@@ -2290,6 +2286,14 @@ Global
{EBED240C-8702-452D-B764-6DB9DA9179AF}.Release|ARM64.Build.0 = Release|ARM64
{EBED240C-8702-452D-B764-6DB9DA9179AF}.Release|x64.ActiveCfg = Release|x64
{EBED240C-8702-452D-B764-6DB9DA9179AF}.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
@@ -2529,6 +2533,7 @@ Global
{08F9155D-B6DC-46E5-9C83-AF60B655898B} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
{4382A954-179A-4078-92AF-715187DFFF50} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
{EBED240C-8702-452D-B764-6DB9DA9179AF} = {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}
177 changes: 177 additions & 0 deletions src/modules/registrypreview/RegistryPreview.FuzzTests/FuzzTests.cs
Original file line number Diff line number Diff line change
@@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the comments, the result represents registry file content, so the name filenameText might be confusing. Would registryContent be a better choice?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I also think so,registryContent would be a better choice than filenameText. I will modify it.


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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that you're using switch (line), which relies on the original line, but you convert lineLower to lowercase for comparison. Which one do you actually intend to use?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intend to use the line, and the lineLower needs to be removed.

{
case REGISTRYHEADER4:
case REGISTRYHEADER5:
return true;
default:
return false;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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)]
Original file line number Diff line number Diff line change
@@ -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
}
]
}
Original file line number Diff line number Diff line change
@@ -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>
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.