Skip to content

Commit a00f56e

Browse files
chenmy77lei9444
andauthored
[Fuzz] Add Fuzz testing for RegistryPreview (#37607)
* Add fuzz test cases * add fuzz tests framework in registrypreview * add registrypreview fuzzing code * add annotations and change net7.0--net8.0 * merge main into code * add registry fuzz sln * change fuzzing tests scope * remove unuse annotations * fix typos * change public parser to internel and private * fix linelower error and modify filenametext to registryContent * Revert "fix linelower error and modify filenametext to registryContent" This reverts commit e8269b8. * add fuzz tests in sln * modify typos * clean code Co-authored-by: leileizhang <[email protected]> * add annotations --------- Co-authored-by: leileizhang <[email protected]>
1 parent 2b7307d commit a00f56e

File tree

7 files changed

+465
-62
lines changed

7 files changed

+465
-62
lines changed

PowerToys.sln

+11
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hosts.FuzzTests", "src\modu
646646
EndProject
647647
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hosts.UITests", "src\modules\Hosts\Hosts.UITests\Hosts.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}"
648648
EndProject
649+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RegistryPreview.FuzzTests", "src\modules\registrypreview\RegistryPreview.FuzzTests\RegistryPreview.FuzzTests.csproj", "{5702B3CC-8575-48D5-83D8-15BB42269CD3}"
650+
EndProject
649651
Global
650652
GlobalSection(SolutionConfigurationPlatforms) = preSolution
651653
Debug|ARM64 = Debug|ARM64
@@ -2294,6 +2296,14 @@ Global
22942296
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|ARM64.Build.0 = Release|ARM64
22952297
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|x64.ActiveCfg = Release|x64
22962298
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|x64.Build.0 = Release|x64
2299+
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Debug|ARM64.ActiveCfg = Debug|ARM64
2300+
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Debug|ARM64.Build.0 = Debug|ARM64
2301+
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Debug|x64.ActiveCfg = Debug|x64
2302+
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Debug|x64.Build.0 = Debug|x64
2303+
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Release|ARM64.ActiveCfg = Release|ARM64
2304+
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Release|ARM64.Build.0 = Release|ARM64
2305+
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Release|x64.ActiveCfg = Release|x64
2306+
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Release|x64.Build.0 = Release|x64
22972307
EndGlobalSection
22982308
GlobalSection(SolutionProperties) = preSolution
22992309
HideSolutionNode = FALSE
@@ -2534,6 +2544,7 @@ Global
25342544
{4382A954-179A-4078-92AF-715187DFFF50} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
25352545
{EBED240C-8702-452D-B764-6DB9DA9179AF} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
25362546
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
2547+
{5702B3CC-8575-48D5-83D8-15BB42269CD3} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
25372548
EndGlobalSection
25382549
GlobalSection(ExtensibilityGlobals) = postSolution
25392550
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Copyright (c) Microsoft Corporation
2+
// The Microsoft Corporation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
using System;
5+
using System.Diagnostics;
6+
using System.Globalization;
7+
using System.Resources;
8+
using Microsoft.VisualStudio.TestTools.UnitTesting;
9+
using Microsoft.Win32;
10+
using RegistryPreviewUILib;
11+
12+
namespace RegistryPreview.FuzzTests
13+
{
14+
public class FuzzTests
15+
{
16+
private const string REGISTRYHEADER4 = "regedit4";
17+
private const string REGISTRYHEADER5 = "windows registry editor version 5.00";
18+
private const string KEYIMAGE = "ms-appx:///Assets/RegistryPreview/folder32.png";
19+
private const string DELETEDKEYIMAGE = "ms-appx:///Assets/RegistryPreview/deleted-folder32.png";
20+
21+
// Case 1: Fuzz test for CheckKeyLineForBrackets
22+
public static void FuzzCheckKeyLineForBrackets(ReadOnlySpan<byte> input)
23+
{
24+
string registryLine;
25+
26+
// Simulate registry file content as registryContent
27+
var registryContent = GenerateRegistryHeader(input);
28+
29+
string[] registryLines = registryContent.Split("\r");
30+
31+
if (registryLines.Length <= 1)
32+
{
33+
return;
34+
}
35+
36+
// REG files have to start with one of two headers and it's case-insensitive
37+
// The header in the registry file is either REGISTRYHEADER4 or REGISTRYHEADER5
38+
registryLine = registryLines[0];
39+
40+
// Check if the registry header is valid
41+
if (!IsValidRegistryHeader(registryLine))
42+
{
43+
return;
44+
}
45+
46+
int index = 1;
47+
registryLine = registryLines[index]; // Extract content after the header
48+
49+
ParseHelper.ProcessRegistryLine(registryLine);
50+
if (registryLine.StartsWith("[-", StringComparison.InvariantCulture))
51+
{
52+
// remove the - as we won't need it but it will get special treatment in the UI
53+
registryLine = registryLine.Remove(1, 1);
54+
55+
string imageName = DELETEDKEYIMAGE;
56+
57+
// Fuzz test for the CheckKeyLineForBrackets method
58+
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
59+
}
60+
else if (registryLine.StartsWith('['))
61+
{
62+
string imageName = KEYIMAGE;
63+
64+
// Fuzz test for the CheckKeyLineForBrackets method
65+
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
66+
}
67+
else
68+
{
69+
return;
70+
}
71+
}
72+
73+
// Case 2: Fuzz test for StripFirstAndLast
74+
public static void FuzzStripFirstAndLast(ReadOnlySpan<byte> input)
75+
{
76+
string registryLine;
77+
78+
var registryContent = GenerateRegistryHeader(input);
79+
80+
registryContent = registryContent.Replace("\r\n", "\r");
81+
string[] registryLines = registryContent.Split("\r");
82+
83+
if (registryLines.Length <= 1)
84+
{
85+
return;
86+
}
87+
88+
// REG files have to start with one of two headers and it's case-insensitive
89+
registryLine = registryLines[0];
90+
91+
if (!IsValidRegistryHeader(registryLine))
92+
{
93+
return;
94+
}
95+
96+
int index = 1;
97+
registryLine = registryLines[index];
98+
99+
ParseHelper.ProcessRegistryLine(registryLine);
100+
101+
if (registryLine.StartsWith("[-", StringComparison.InvariantCulture))
102+
{
103+
// remove the - as we won't need it but it will get special treatment in the UI
104+
registryLine = registryLine.Remove(1, 1);
105+
106+
string imageName = DELETEDKEYIMAGE;
107+
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
108+
109+
// Fuzz test for the StripFirstAndLast method
110+
registryLine = ParseHelper.StripFirstAndLast(registryLine);
111+
}
112+
else if (registryLine.StartsWith('['))
113+
{
114+
string imageName = KEYIMAGE;
115+
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
116+
117+
// Fuzz test for the StripFirstAndLast method
118+
registryLine = ParseHelper.StripFirstAndLast(registryLine);
119+
}
120+
else if (registryLine.StartsWith('"') && registryLine.EndsWith("=-", StringComparison.InvariantCulture))
121+
{
122+
// remove "=-"
123+
registryLine = registryLine[..^2];
124+
125+
// remove the "'s without removing all of them
126+
// Fuzz test for the StripFirstAndLast method
127+
registryLine = ParseHelper.StripFirstAndLast(registryLine);
128+
}
129+
else if (registryLine.StartsWith('"'))
130+
{
131+
int equal = registryLine.IndexOf('=');
132+
if ((equal < 0) || (equal > registryLine.Length - 1))
133+
{
134+
// something is very wrong
135+
return;
136+
}
137+
138+
// set the name and the value
139+
string name = registryLine.Substring(0, equal);
140+
141+
// trim the whitespace and quotes from the name
142+
name = name.Trim();
143+
144+
// Fuzz test for the StripFirstAndLast method
145+
name = ParseHelper.StripFirstAndLast(name);
146+
147+
// Clean out any escaped characters in the value, only for the preview
148+
name = ParseHelper.StripEscapedCharacters(name);
149+
150+
// set the value
151+
string value = registryLine.Substring(equal + 1);
152+
153+
// trim the whitespace from the value
154+
value = value.Trim();
155+
156+
// if the first character is a " then this is a string value, so find the last most " which will avoid comments
157+
if (value.StartsWith('"'))
158+
{
159+
int last = value.LastIndexOf('"');
160+
if (last >= 0)
161+
{
162+
value = value.Substring(0, last + 1);
163+
}
164+
}
165+
166+
if (value.StartsWith('"') && value.EndsWith('"'))
167+
{
168+
value = ParseHelper.StripFirstAndLast(value);
169+
}
170+
}
171+
else
172+
{
173+
return;
174+
}
175+
}
176+
177+
public static string GenerateRegistryHeader(ReadOnlySpan<byte> input)
178+
{
179+
string header = new Random().Next(2) == 0 ? REGISTRYHEADER4 : REGISTRYHEADER5;
180+
181+
string inputText = System.Text.Encoding.UTF8.GetString(input);
182+
string registryContent = header + "\r" + inputText;
183+
184+
return registryContent;
185+
}
186+
187+
private static bool IsValidRegistryHeader(string line)
188+
{
189+
// Convert the line to lowercase once for comparison
190+
switch (line)
191+
{
192+
case REGISTRYHEADER4:
193+
case REGISTRYHEADER5:
194+
return true;
195+
default:
196+
return false;
197+
}
198+
}
199+
}
200+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (c) Microsoft Corporation
2+
// The Microsoft Corporation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright (c) Microsoft Corporation
2+
// The Microsoft Corporation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
{
5+
"configVersion": 3,
6+
"entries": [
7+
{
8+
"fuzzer": {
9+
"$type": "libfuzzerDotNet",
10+
"dll": "RegistryPreview.FuzzTests.dll",
11+
"class": "RegistryPreview.FuzzTests.FuzzTests",
12+
"method": "FuzzCheckKeyLineForBrackets",
13+
"FuzzingTargetBinaries": [
14+
"PowerToys.RegistryPreview.dll"
15+
]
16+
},
17+
"adoTemplate": {
18+
// supply the values appropriate to your
19+
// project, where bugs will be filed
20+
"org": "microsoft",
21+
"project": "OS",
22+
"AssignedTo": "[email protected]",
23+
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
24+
"IterationPath": "OS\\Future"
25+
},
26+
"jobNotificationEmail": "[email protected]",
27+
"skip": false,
28+
"rebootAfterSetup": false,
29+
"oneFuzzJobs": [
30+
// at least one job is required
31+
{
32+
"projectName": "RegistryPreview",
33+
"targetName": "RegistryPreview-dotnet-CheckKeyLineForBrackets-fuzzer"
34+
}
35+
],
36+
"jobDependencies": [
37+
// this should contain, at minimum,
38+
// the DLL and PDB files
39+
// you will need to add any other files required
40+
// (globs are supported)
41+
"RegistryPreview.FuzzTests.dll",
42+
"RegistryPreview.FuzzTests.pdb",
43+
"Microsoft.Windows.SDK.NET.dll",
44+
"WinRT.Runtime.dll"
45+
],
46+
"SdlWorkItemId": 49911822
47+
},
48+
{
49+
"fuzzer": {
50+
"$type": "libfuzzerDotNet",
51+
"dll": "RegistryPreview.FuzzTests.dll",
52+
"class": "RegistryPreview.FuzzTests.FuzzTests",
53+
"method": "FuzzStripFirstAndLast",
54+
"FuzzingTargetBinaries": [
55+
"PowerToys.RegistryPreview.dll"
56+
]
57+
},
58+
"adoTemplate": {
59+
// supply the values appropriate to your
60+
// project, where bugs will be filed
61+
"org": "microsoft",
62+
"project": "OS",
63+
"AssignedTo": "[email protected]",
64+
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
65+
"IterationPath": "OS\\Future"
66+
},
67+
"jobNotificationEmail": "[email protected]",
68+
"skip": false,
69+
"rebootAfterSetup": false,
70+
"oneFuzzJobs": [
71+
// at least one job is required
72+
{
73+
"projectName": "RegistryPreview",
74+
"targetName": "RegistryPreview-dotnet-StripFirstAndLasts-fuzzer"
75+
}
76+
],
77+
"jobDependencies": [
78+
// this should contain, at minimum,
79+
// the DLL and PDB files
80+
// you will need to add any other files required
81+
// (globs are supported)
82+
"RegistryPreview.FuzzTests.dll",
83+
"RegistryPreview.FuzzTests.pdb",
84+
"Microsoft.Windows.SDK.NET.dll",
85+
"WinRT.Runtime.dll"
86+
],
87+
"SdlWorkItemId": 49911822
88+
}
89+
]
90+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
5+
<Platforms>x64;ARM64</Platforms>
6+
<LangVersion>latest</LangVersion>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<Nullable>enable</Nullable>
9+
</PropertyGroup>
10+
11+
<PropertyGroup>
12+
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\RegistryPreview.FuzzTests\</OutputPath>
13+
</PropertyGroup>
14+
15+
<ItemGroup>
16+
<Compile Include="..\RegistryPreviewUILib\ParseHelper.cs" Link="ParseHelper.cs" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<Content Include="OneFuzzConfig.json">
21+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
22+
</Content>
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<PackageReference Include="MSTest" />
27+
</ItemGroup>
28+
29+
<ItemGroup>
30+
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
31+
</ItemGroup>
32+
33+
</Project>

0 commit comments

Comments
 (0)