diff --git a/.pipelines/build-job.yml b/.pipelines/build-job.yml
index f883f0b4bb..577b58517c 100644
--- a/.pipelines/build-job.yml
+++ b/.pipelines/build-job.yml
@@ -64,20 +64,32 @@ jobs:
pool: ${{ parameters.pool }}
variables:
- NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS: 60
- NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS: 60
- ob_outputDirectory: '$(Build.SourcesDirectory)\out'
- ob_artifactBaseName: 'drop_wsl'
- ob_artifactSuffix: '${{ parameters.artifactSuffix }}'
- packageStagingDir: '$(Build.SourcesDirectory)\packageStagingDir'
- ob_sdl_codeSignValidation_excludes: -|**\*.ps1;-|**\testbin\**
- ${{ if parameters.includeCodeQL }}:
- Codeql.PublishDatabaseLog: true
- Codeql.SourceRoot: src
- ${{ if eq(parameters.isRelease, true) }}:
- packageInputDirArg: '-DPACKAGE_INPUT_DIR=$(packageStagingDir)'
- ${{ else }}:
- packageInputDirArg: ''
+ - template: variables.yml
+ - name: NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS
+ value: 60
+ - name: NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS
+ value: 60
+ - name: ob_outputDirectory
+ value: '$(Build.SourcesDirectory)\out'
+ - name: ob_artifactBaseName
+ value: 'drop_wsl'
+ - name: ob_artifactSuffix
+ value: '${{ parameters.artifactSuffix }}'
+ - name: packageStagingDir
+ value: '$(Build.SourcesDirectory)\packageStagingDir'
+ - name: ob_sdl_codeSignValidation_excludes
+ value: -|**\*.ps1;-|**\testbin\**
+ - ${{ if parameters.includeCodeQL }}:
+ - name: Codeql.PublishDatabaseLog
+ value: true
+ - name: Codeql.SourceRoot
+ value: src
+ - ${{ if eq(parameters.isRelease, true) }}:
+ - name: packageInputDirArg
+ value: '-DPACKAGE_INPUT_DIR=$(packageStagingDir)'
+ - ${{ else }}:
+ - name: packageInputDirArg
+ value: ''
steps:
@@ -124,7 +136,7 @@ jobs:
displayName: "CMake ${{ parameters.platform }}"
inputs:
workingDirectory: "."
- cmakeArgs: . --fresh -A ${{ parameters.platform }} -DCMAKE_BUILD_TYPE=Release -DCMAKE_SYSTEM_VERSION=10.0.26100.0 -DPACKAGE_VERSION=$(version.WSL_PACKAGE_VERSION) -DWSL_NUGET_PACKAGE_VERSION=$(version.WSL_NUGET_PACKAGE_VERSION) -DSKIP_PACKAGE_SIGNING=${{ parameters.isRelease }} -DOFFICIAL_BUILD=${{ parameters.isRelease }} -DINCLUDE_PACKAGE_STAGE=${{ or(parameters.isRelease, parameters.isNightly) }} -DPIPELINE_BUILD_ID=$(Build.BuildId) -DVSO_ORG=${{ parameters.vsoOrg }} -DVSO_PROJECT=${{ parameters.vsoProject }} -DWSL_BUILD_WSL_SETTINGS=true -DWSL_INCLUDE_SDK_CSHARP=true $(packageInputDirArg)\${{ parameters.platform }}
+ cmakeArgs: . --fresh -A ${{ parameters.platform }} -DCMAKE_BUILD_TYPE=Release -DCMAKE_SYSTEM_VERSION=$(cmakeSystemVersion) -DPACKAGE_VERSION=$(version.WSL_PACKAGE_VERSION) -DWSL_NUGET_PACKAGE_VERSION=$(version.WSL_NUGET_PACKAGE_VERSION) -DSKIP_PACKAGE_SIGNING=${{ parameters.isRelease }} -DOFFICIAL_BUILD=${{ parameters.isRelease }} -DINCLUDE_PACKAGE_STAGE=${{ or(parameters.isRelease, parameters.isNightly) }} -DPIPELINE_BUILD_ID=$(Build.BuildId) -DVSO_ORG=${{ parameters.vsoOrg }} -DVSO_PROJECT=${{ parameters.vsoProject }} -DWSL_BUILD_WSL_SETTINGS=true -DWSL_INCLUDE_SDK_CSHARP=true $(packageInputDirArg)\${{ parameters.platform }}
# Workaround for WSL Settings NuGet restore authentication issue
- script: _deps\nuget.exe restore -NonInteractive
diff --git a/.pipelines/fuzzing-stage.yml b/.pipelines/fuzzing-stage.yml
new file mode 100644
index 0000000000..7c76791ed8
--- /dev/null
+++ b/.pipelines/fuzzing-stage.yml
@@ -0,0 +1,80 @@
+# Fuzzing stage — builds fuzz harnesses with ASAN/SanCov and submits to OneFuzz.
+
+parameters:
+ - name: fuzzTargets
+ type: object
+ default:
+ - WslcCliArgumentFuzzing
+ - WslcSdkFuzzing
+ - WslcWinRtFuzzing
+
+stages:
+ - stage: fuzzing
+ dependsOn: []
+ jobs:
+ - job: fuzzing_x64
+ displayName: "Build and submit WSLC fuzz targets (x64)"
+ timeoutInMinutes: 120
+ pool:
+ type: windows
+
+ # OneFuzz job notification (a team alias) and the bug assignee (a person) must be defined as secret pipeline variables.
+ variables:
+ - template: variables.yml
+ - name: ob_outputDirectory
+ value: '$(Build.SourcesDirectory)\out'
+ - name: fuzzingBinDir
+ value: '$(Build.SourcesDirectory)\bin\x64\Release'
+ - name: fuzzingStagingDir
+ value: '$(ob_outputDirectory)\fuzzing'
+
+ steps:
+ - task: CMake@1
+ displayName: "CMake configure (fuzzing)"
+ inputs:
+ workingDirectory: "."
+ cmakeArgs: . --fresh -A x64 -DCMAKE_BUILD_TYPE=Release -DCMAKE_SYSTEM_VERSION=$(cmakeSystemVersion) -DWSL_BUILD_FUZZING=true -DONEFUZZ_NOTIFICATION_EMAIL=$(oneFuzzNotificationEmail) -DONEFUZZ_ASSIGNED_TO=$(oneFuzzAssignedTo)
+
+ # Build the fuzzing targets, and the MSI for setting up the VMs
+ - task: CMake@1
+ displayName: "CMake build (fuzzing)"
+ inputs:
+ workingDirectory: "."
+ cmakeArgs: --build . --config Release --target ${{ join(' ', parameters.fuzzTargets) }} msipackage -- -m
+
+ - ${{ each target in parameters.fuzzTargets }}:
+ - task: PowerShell@2
+ displayName: "Stage ${{ target }} binaries"
+ inputs:
+ targetType: inline
+ script: |
+ $binDir = "$(fuzzingBinDir)"
+ $dropDir = "$(fuzzingStagingDir)"
+ New-Item -ItemType Directory -Path $dropDir -Force
+ Copy-Item "$binDir\${{ target }}.*" $dropDir
+
+ - task: PowerShell@2
+ displayName: "Stage shared fuzzing artifacts"
+ inputs:
+ targetType: inline
+ script: |
+ $binDir = "$(fuzzingBinDir)"
+ $dropDir = "$(fuzzingStagingDir)"
+
+ # CMake places OneFuzzConfig.json, setup.ps1, and per-harness extra dependencies
+ # (e.g. wslcsdk.dll/pdb) under the fuzzing output dir.
+ Copy-Item "$binDir\fuzzing\*" $dropDir -Force
+
+ # Shared runtime deps. CMake copies the ASAN runtime next to the build output.
+ Copy-Item "$binDir\wsl.msi" $dropDir
+ Copy-Item "$binDir\clang_rt.asan_dynamic*.dll" $dropDir
+
+ Get-ChildItem $dropDir -Recurse | Format-Table Name, Length
+
+ - task: onefuzz-task@0
+ displayName: "Submit to OneFuzz"
+ inputs:
+ onefuzzOSes: 'Windows'
+ env:
+ onefuzzDropDirectory: $(fuzzingStagingDir)
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
diff --git a/.pipelines/variables.yml b/.pipelines/variables.yml
new file mode 100644
index 0000000000..afc95bd108
--- /dev/null
+++ b/.pipelines/variables.yml
@@ -0,0 +1,3 @@
+variables:
+ - name: cmakeSystemVersion
+ value: '10.0.26100.0'
diff --git a/.pipelines/wsl-build-nightly-onebranch.yml b/.pipelines/wsl-build-nightly-onebranch.yml
index 1434dd233d..efeb0d2a07 100644
--- a/.pipelines/wsl-build-nightly-onebranch.yml
+++ b/.pipelines/wsl-build-nightly-onebranch.yml
@@ -47,6 +47,8 @@ extends:
vsoOrg: microsoft
vsoProject: Microsoft.WSL
+ - template: fuzzing-stage.yml@self
+
- template: test-stage.yml@self
parameters:
rs_prerelease_only: false
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c88fbb8e90..2d2515e848 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -179,6 +179,10 @@ if (NOT DEFINED WSL_INCLUDE_SDK_CSHARP)
set(WSL_INCLUDE_SDK_CSHARP false)
endif ()
+if (NOT DEFINED WSL_BUILD_FUZZING)
+ set(WSL_BUILD_FUZZING false)
+endif ()
+
find_commit_hash(COMMIT_HASH)
if (NOT PACKAGE_VERSION)
@@ -314,18 +318,48 @@ endif ()
string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) # make sure /Zi is removed from the debug flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj /W3 /WX /ZH:SHA_256")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Z7 -DDEBUG -DDBG")
-set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi /guard:cf /Qspectre")
+set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
+
+if (WSL_BUILD_FUZZING)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address")
+ add_compile_definitions(_DISABLE_VECTOR_ANNOTATION _DISABLE_STRING_ANNOTATION)
+
+ # All instrumented binaries import the dynamic ASAN runtime.
+ # We resolve its path so that we can include it in the MSI; we can find it next to the compiler.
+ # We also copy it next to the build output so that the pipeline can find it easily.
+ if (${TARGET_PLATFORM} STREQUAL "arm64")
+ set(ASAN_ARCH "aarch64")
+ else()
+ set(ASAN_ARCH "x86_64")
+ endif()
+ get_filename_component(MSVC_BIN_DIR ${CMAKE_CXX_COMPILER} DIRECTORY)
+ set(ASAN_RUNTIME_DLL ${MSVC_BIN_DIR}/clang_rt.asan_dynamic-${ASAN_ARCH}.dll)
+ file(COPY ${ASAN_RUNTIME_DLL} DESTINATION ${BIN})
+endif ()
+
+if (WSL_ENABLE_FUZZING_REPLAY)
+ add_compile_definitions(WSL_ENABLE_FUZZING_REPLAY)
+endif ()
# Linker flags
-set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /debug:full /debugtype:cv,fixup /guard:cf /DYNAMICBASE")
-set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /debug:full /debugtype:cv,fixup /guard:cf /DYNAMICBASE")
+set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /debug:full /debugtype:cv,fixup /DYNAMICBASE")
+set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /debug:full /debugtype:cv,fixup /DYNAMICBASE")
if (DEFINED SOURCELINK_JSON)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /SOURCELINK:\"${SOURCELINK_JSON}\"")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SOURCELINK:\"${SOURCELINK_JSON}\"")
endif()
-if (${TARGET_PLATFORM} STREQUAL "x64")
- set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /CETCOMPAT")
- set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /CETCOMPAT")
+
+# When WSL_BUILD_FUZZING is enabled, the entire build is compiled with ASAN.
+# /fsanitize=address is incompatible with /guard:cf, /Qspectre, and /CETCOMPAT,
+# so those are omitted for fuzzing builds.
+if (NOT WSL_BUILD_FUZZING)
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /guard:cf /Qspectre")
+ set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /guard:cf")
+ set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /guard:cf")
+ if (${TARGET_PLATFORM} STREQUAL "x64")
+ set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /CETCOMPAT")
+ set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /CETCOMPAT")
+ endif()
endif()
# Common link libraries
@@ -554,6 +588,10 @@ if (WSL_BUILD_WSL_SETTINGS)
add_subdirectory(src/windows/wslsettings)
endif()
+if (WSL_BUILD_FUZZING)
+ add_subdirectory(src/windows/fuzzing)
+endif()
+
add_subdirectory(src/linux/netlinkutil)
add_subdirectory(src/linux/mountutil)
add_subdirectory(src/linux/plan9)
diff --git a/UserConfig.cmake.sample b/UserConfig.cmake.sample
index cedc89c3b5..216b2fb767 100644
--- a/UserConfig.cmake.sample
+++ b/UserConfig.cmake.sample
@@ -56,3 +56,11 @@ endif()
# # error - block the commit when formatting issues are found
# # fix - automatically fix formatting and re-stage files
# set(WSL_PRE_COMMIT_MODE "warn")
+
+# # Uncomment to build fuzzing targets with ASAN and SanitizerCoverage for OneFuzz.
+# # See src/windows/fuzzing/README.md for details.
+# set(WSL_BUILD_FUZZING true)
+#
+# # Enable fuzzing replay mode: builds harnesses without libFuzzer so they can be run standalone
+# # with a corpus file as argument. Useful for reproducing crashes locally.
+# set(WSL_ENABLE_FUZZING_REPLAY true)
diff --git a/msipackage/CMakeLists.txt b/msipackage/CMakeLists.txt
index 99586c9727..f8f76487e6 100644
--- a/msipackage/CMakeLists.txt
+++ b/msipackage/CMakeLists.txt
@@ -32,6 +32,13 @@ foreach(binary ${LINUX_BINARIES})
list(APPEND BINARIES_DEPENDENCIES "${BIN}/${binary}")
endforeach()
+# When building with ASAN (fuzzing), the instrumented binaries import the dynamic ASAN runtime.
+# Ship it alongside them in INSTALLDIR so they can load at runtime.
+# ASAN_RUNTIME_DLL is resolved in the top-level CMakeLists.
+if (WSL_BUILD_FUZZING)
+ list(APPEND BINARIES_DEPENDENCIES ${ASAN_RUNTIME_DLL})
+endif()
+
if (${WSL_BUILD_THIN_PACKAGE})
set(COMPRESS_PACKAGE "no")
else()
diff --git a/msipackage/package.wix.in b/msipackage/package.wix.in
index 18b79acead..017cec96b7 100644
--- a/msipackage/package.wix.in
+++ b/msipackage/package.wix.in
@@ -38,6 +38,10 @@
+
+
+
+
diff --git a/src/windows/common/CMakeLists.txt b/src/windows/common/CMakeLists.txt
index 886e2b6b95..13c7097c78 100644
--- a/src/windows/common/CMakeLists.txt
+++ b/src/windows/common/CMakeLists.txt
@@ -144,14 +144,29 @@ set(HEADERS
WSLCSessionDefaults.h
)
-add_library(common STATIC ${SOURCES} ${HEADERS})
-add_dependencies(common wslserviceidl localization wslservicemc wslinstalleridl yaml-cpp)
+# Adds a target for the common static library.
+# This is factored into a function so that we can have multiple variants with different options.
+function(add_common_library TARGET_NAME)
+ add_library(${TARGET_NAME} STATIC ${SOURCES} ${HEADERS})
+ add_dependencies(${TARGET_NAME} wslserviceidl localization wslservicemc wslinstalleridl yaml-cpp)
+ target_precompile_headers(${TARGET_NAME} PRIVATE precomp.h)
+ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER windows)
+ target_include_directories(${TARGET_NAME} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/../service/mc/${TARGET_PLATFORM}/${CMAKE_BUILD_TYPE})
+endfunction()
-target_precompile_headers(common PRIVATE precomp.h)
-set_target_properties(common PROPERTIES FOLDER windows)
-target_include_directories(common PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/../service/mc/${TARGET_PLATFORM}/${CMAKE_BUILD_TYPE})
+add_common_library(common)
# WSLCUserSettings.cpp uses yaml-cpp headers.
set_source_files_properties(WSLCUserSettings.cpp PROPERTIES
INCLUDE_DIRECTORIES "${yaml-cpp_SOURCE_DIR}/include"
COMPILE_DEFINITIONS "YAML_CPP_STATIC_DEFINE")
+
+# For Fuzzing builds, there are some targets that cannot be built with ASAN instrumentation.
+# These targets use a non-instrumented copy of the common library.
+if (WSL_BUILD_FUZZING)
+ # Remove ASAN from all targets in this directory, then add it back only for the base variant.
+ string(REPLACE "/fsanitize=address" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+ target_compile_options(common PRIVATE /fsanitize=address)
+
+ add_common_library(common_noasan)
+endif()
diff --git a/src/windows/fuzzing/CMakeLists.txt b/src/windows/fuzzing/CMakeLists.txt
new file mode 100644
index 0000000000..2874ed96ac
--- /dev/null
+++ b/src/windows/fuzzing/CMakeLists.txt
@@ -0,0 +1,89 @@
+set(FUZZING_OUT_DIR ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CMAKE_BUILD_TYPE}/fuzzing)
+
+# OneFuzz job notification (a team alias) and bug assignee (a person), substituted into
+# OneFuzzConfigEntry.json.in. The pipeline passes these via -D from secret variables; local
+# builds leave them empty since they never submit to OneFuzz.
+if (NOT DEFINED ONEFUZZ_NOTIFICATION_EMAIL)
+ set(ONEFUZZ_NOTIFICATION_EMAIL "")
+endif()
+if (NOT DEFINED ONEFUZZ_ASSIGNED_TO)
+ set(ONEFUZZ_ASSIGNED_TO "")
+endif()
+
+function(add_fuzzing_harness TARGET)
+ cmake_parse_arguments(ARG "" "TARGET_NAME" "TARGET_BINARIES;LINK_LIBRARIES;EXTRA_DEPENDENCIES;INCLUDE_DIRECTORIES" ${ARGN})
+
+ add_executable(${TARGET} ${TARGET}.cpp)
+ target_precompile_headers(${TARGET} REUSE_FROM common)
+
+ if (NOT WSL_ENABLE_FUZZING_REPLAY)
+ target_compile_options(${TARGET} PRIVATE /fsanitize=fuzzer)
+ endif()
+
+ # This is only for tracking which binaries have fuzzing for compliance
+ set(TARGET_BINARIES "")
+ if (ARG_TARGET_BINARIES)
+ list(JOIN ARG_TARGET_BINARIES "\", \"" TARGET_BINARIES)
+ set(TARGET_BINARIES "\"${TARGET_BINARIES}\"")
+ endif()
+
+ if(ARG_LINK_LIBRARIES)
+ target_link_libraries(${TARGET} PRIVATE ${ARG_LINK_LIBRARIES})
+ endif()
+
+ if(ARG_INCLUDE_DIRECTORIES)
+ target_include_directories(${TARGET} PRIVATE ${ARG_INCLUDE_DIRECTORIES})
+ endif()
+
+ set_target_properties(${TARGET} PROPERTIES FOLDER fuzzing)
+
+ if(ARG_TARGET_NAME)
+ set(TARGET_NAME ${ARG_TARGET_NAME})
+ else()
+ set(TARGET_NAME ${TARGET})
+ endif()
+
+ # Add extra dependencies to the entry, and stage them into the shared fuzzing output dir
+ set(EXTRA_DEPENDENCIES_JSON "")
+ foreach(dep IN LISTS ARG_EXTRA_DEPENDENCIES)
+ string(APPEND EXTRA_DEPENDENCIES_JSON ", \"${dep}\"")
+
+ # Dependency file will be in the main binary output dir after build
+ add_custom_command(TARGET ${TARGET} POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CMAKE_BUILD_TYPE}/${dep}
+ ${FUZZING_OUT_DIR}/${dep})
+ endforeach()
+
+ file(READ ${CMAKE_CURRENT_SOURCE_DIR}/OneFuzzConfigEntry.json.in ENTRY_TEMPLATE)
+ string(CONFIGURE "${ENTRY_TEMPLATE}" ENTRY_JSON)
+
+ # Append this harness's entry to OneFuzzConfig.json (written after all harnesses).
+ list(APPEND ONEFUZZ_ENTRIES "${ENTRY_JSON}")
+ set(ONEFUZZ_ENTRIES "${ONEFUZZ_ENTRIES}" PARENT_SCOPE)
+endfunction()
+
+configure_file(setup.ps1 ${FUZZING_OUT_DIR}/setup.ps1 COPYONLY)
+
+add_fuzzing_harness(WslcCliArgumentFuzzing
+ TARGET_NAME cliArgumentParser
+ TARGET_BINARIES wslc.exe
+ LINK_LIBRARIES wslclib)
+
+add_fuzzing_harness(WslcSdkFuzzing
+ TARGET_NAME sdkApiFlow
+ TARGET_BINARIES wslcsdk.dll
+ LINK_LIBRARIES wslcsdk
+ INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/src/windows/WslcSDK
+ EXTRA_DEPENDENCIES "wslcsdk.dll" "wslcsdk.pdb")
+
+add_fuzzing_harness(WslcWinRtFuzzing
+ TARGET_NAME winrtApiFlow
+ TARGET_BINARIES wslcsdk.dll
+ LINK_LIBRARIES wslcsdk
+ INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/src/windows/WslcSDK ${CMAKE_BINARY_DIR}/src/windows/WslcSDK/winrt/${TARGET_PLATFORM}/${CMAKE_BUILD_TYPE}
+ EXTRA_DEPENDENCIES "wslcsdk.dll" "wslcsdk.pdb")
+
+# Write OneFuzzConfig.json with one entry per harness.
+list(JOIN ONEFUZZ_ENTRIES ",\n" ONEFUZZ_ENTRIES)
+configure_file(OneFuzzConfig.json.in ${FUZZING_OUT_DIR}/OneFuzzConfig.json)
diff --git a/src/windows/fuzzing/FuzzingHarness.h b/src/windows/fuzzing/FuzzingHarness.h
new file mode 100644
index 0000000000..92baaa95cf
--- /dev/null
+++ b/src/windows/fuzzing/FuzzingHarness.h
@@ -0,0 +1,107 @@
+// Copyright (C) Microsoft Corporation. All rights reserved.
+
+// Shared utilities for fuzzing harnesses.
+// Provides:
+// * FuzzInput - a cursor over the raw libFuzzer buffer
+// * main() - for replaying corpus inputs without the libFuzzer runtime (e.g. for debugging or OneFuzz repros)
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+// Cursor over fuzz input bytes. Harnesses construct this from LLVMFuzzerTestOneInput args
+// and call typed Read methods. All reads are bounds-checked and return default values on exhaustion.
+class FuzzInput
+{
+public:
+ FuzzInput(const uint8_t* data, size_t size) : m_data(data), m_size(size), m_offset(0)
+ {
+ }
+
+ bool Empty() const
+ {
+ return m_offset >= m_size;
+ }
+
+ size_t Remaining() const
+ {
+ return m_offset < m_size ? m_size - m_offset : 0;
+ }
+
+ // Read a fixed-size value (uint8_t, uint16_t, uint32_t, etc.)
+ template
+ T Read()
+ {
+ T value{};
+ if (m_offset + sizeof(T) <= m_size)
+ {
+ std::memcpy(&value, m_data + m_offset, sizeof(T));
+ m_offset += sizeof(T);
+ }
+ return value;
+ }
+
+ // Read a null-terminated narrow string (up to maxLen chars)
+ std::string ReadString(size_t maxLen = 64)
+ {
+ std::string result;
+ while (m_offset < m_size && result.size() < maxLen)
+ {
+ char ch = static_cast(m_data[m_offset++]);
+ if (ch == '\0')
+ {
+ break;
+ }
+ result.push_back(ch);
+ }
+ return result;
+ }
+
+ // Read a null-terminated wide string (byte pairs as little-endian wchar_t, up to maxLen chars)
+ std::wstring ReadWideString(size_t maxLen = 64)
+ {
+ std::wstring result;
+ while (m_offset + 1 < m_size && result.size() < maxLen)
+ {
+ wchar_t ch = static_cast(m_data[m_offset] | (m_data[m_offset + 1] << 8));
+ m_offset += 2;
+ if (ch == L'\0')
+ {
+ break;
+ }
+ result.push_back(ch);
+ }
+ return result;
+ }
+
+private:
+ const uint8_t* m_data;
+ size_t m_size;
+ size_t m_offset;
+};
+
+#ifdef WSL_ENABLE_FUZZING_REPLAY
+
+#include
+#include
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv);
+
+// Replay mode: run the harness on each argument as a corpus file.
+int main(int argc, char** argv)
+{
+ LLVMFuzzerInitialize(&argc, &argv);
+ for (int i = 1; i < argc; ++i)
+ {
+ std::ifstream stream(argv[i], std::ios_base::in | std::ios_base::binary);
+ std::string contents((std::istreambuf_iterator(stream)), std::istreambuf_iterator());
+ LLVMFuzzerTestOneInput(reinterpret_cast(contents.data()), contents.size());
+ }
+ return 0;
+}
+
+#endif // WSL_ENABLE_FUZZING_REPLAY
diff --git a/src/windows/fuzzing/OneFuzzConfig.json.in b/src/windows/fuzzing/OneFuzzConfig.json.in
new file mode 100644
index 0000000000..fc8229d8ec
--- /dev/null
+++ b/src/windows/fuzzing/OneFuzzConfig.json.in
@@ -0,0 +1,6 @@
+{
+ "ConfigVersion": 3,
+ "Entries": [
+${ONEFUZZ_ENTRIES}
+ ]
+}
diff --git a/src/windows/fuzzing/OneFuzzConfigEntry.json.in b/src/windows/fuzzing/OneFuzzConfigEntry.json.in
new file mode 100644
index 0000000000..b1884a5685
--- /dev/null
+++ b/src/windows/fuzzing/OneFuzzConfigEntry.json.in
@@ -0,0 +1,32 @@
+ {
+ "JobNotificationEmail": "${ONEFUZZ_NOTIFICATION_EMAIL}",
+ "Skip": false,
+ "RebootAfterSetup": true,
+ "SdlWorkItemId": 61752747,
+ "Fuzzer": {
+ "$type": "libfuzzer",
+ "FuzzingTargetBinaries": [ ${TARGET_BINARIES} ],
+ "FuzzingHarnessExecutableName": "${TARGET}.exe"
+ },
+ "OneFuzzJobs": [
+ {
+ "ProjectName": "wsl-fuzzing",
+ "TargetName": "${TARGET_NAME}"
+ }
+ ],
+ "JobDependencies": [
+ "${TARGET}.exe",
+ "${TARGET}.pdb",
+ "clang_rt.asan_dynamic*.dll",
+ "setup.ps1",
+ "wsl.msi"
+ ${EXTRA_DEPENDENCIES_JSON}
+ ],
+ "AdoTemplate": {
+ "Org": "microsoft",
+ "Project": "OS",
+ "AssignedTo": "${ONEFUZZ_ASSIGNED_TO}",
+ "AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\InstaDev",
+ "IterationPath": "OS"
+ }
+ }
\ No newline at end of file
diff --git a/src/windows/fuzzing/README.md b/src/windows/fuzzing/README.md
new file mode 100644
index 0000000000..6000471438
--- /dev/null
+++ b/src/windows/fuzzing/README.md
@@ -0,0 +1,97 @@
+# WSLC Fuzzing
+
+Fuzzing harnesses for WSLC, built with AddressSanitizer (ASAN) and libFuzzer for integration with OneFuzz.
+
+## How It Works
+
+When `WSL_BUILD_FUZZING=true`:
+- The entire build is compiled with `/fsanitize=address` — ASAN detects memory errors at runtime.
+- Fuzzing harness executables get `/fsanitize=fuzzer` — this links the libFuzzer runtime and enables SanitizerCoverage for coverage-guided mutation.
+
+Reference: https://learn.microsoft.com/cpp/sanitizers/asan-building
+
+## Adding a New Harness
+
+1. Create `TargetName.cpp` implementing `LLVMFuzzerTestOneInput` and including `FuzzingHarness.h`
+2. Register it in `CMakeLists.txt`:
+
+```cmake
+add_fuzzing_harness(MyNewFuzzing
+ TARGET_NAME myTarget
+ LINK_LIBRARIES somelib
+ EXTRA_DEPENDENCIES "extra.dll" "extra.pdb")
+```
+
+The function handles compiler flags, PCH, and contributes one entry to `OneFuzzConfig.json`.
+The entry is rendered from `OneFuzzConfigEntry.json.in`; the surrounding file comes from `OneFuzzConfig.json.in`.
+
+## Building
+
+Enable fuzzing in `UserConfig.cmake`:
+
+```cmake
+set(WSL_BUILD_FUZZING true)
+```
+
+Then:
+
+```powershell
+cmake . --fresh
+cmake --build . --config Release --target WslcCliArgumentFuzzing -- -m
+```
+
+The fuzzing build cannot be mixed with a non-ASAN build in the same tree. Use `--fresh` when toggling `WSL_BUILD_FUZZING`.
+
+## Harnesses
+
+| Harness | Target | Description |
+|---------|--------|-------------|
+| `WslcCliArgumentFuzzing` | `ParseArgumentsStateMachine` | CLI argument parser — standalone, no service needed |
+| `WslcSdkFuzzing` | WSLC SDK C API | Session + container creation flow via C functions |
+| `WslcWinRtFuzzing` | WSLC SDK WinRT API | Same flow via C++/WinRT projection |
+
+## OneFuzz Integration
+
+The nightly pipeline (`.pipelines/fuzzing-stage.yml`) builds the harnesses + MSI, stages them into a flat drop, and submits them with `onefuzz-task@0`.
+`OneFuzzConfig.json` holds one entry per harness; shared dependencies (`setup.ps1`, `wsl.msi`, the ASAN runtime) sit alongside the binaries in the drop.
+
+### VM Setup
+
+`setup.ps1` runs on the OneFuzz VM before fuzzing. It enables Hyper-V, enables WSL, and installs `wsl.msi` from the build drop. `RebootAfterSetup` is enabled so features activate before the harness runs.
+
+### Seed Corpus (optional, not yet wired up)
+
+The harnesses run without a seed corpus — libFuzzer starts from an empty corpus and generates its own inputs.
+Seeds only speed up the initial coverage ramp (most useful for the structured SDK/WinRT
+inputs).
+
+To add seeding later, give each entry a `SeedCorpusContainer` in `OneFuzzConfigEntry.json.in` and upload the generated seeds to that container using the OneFuzz CLI.
+`generate-seeds.ps1` produces a corpus per target; the same directories are used for local replay.
+
+### Drop Layout
+
+```
+fuzzing/
+├── OneFuzzConfig.json
+├── clang_rt.asan_dynamic-x86_64.dll
+├── setup.ps1
+├── wsl.msi
+├── .exe
+├── .pdb
+├──
+├── ...
+```
+
+## Local Testing
+
+To build a standalone replay binary (no libFuzzer runtime), add to your `UserConfig.cmake`:
+
+```cmake
+set(WSL_ENABLE_FUZZING_REPLAY true)
+```
+
+Then rebuild. The harness will include a `main()` that replays corpus files:
+
+```powershell
+WslcCliArgumentFuzzing.exe path\to\crash-input
+```
diff --git a/src/windows/fuzzing/WslcCliArgumentFuzzing.cpp b/src/windows/fuzzing/WslcCliArgumentFuzzing.cpp
new file mode 100644
index 0000000000..ce342220fe
--- /dev/null
+++ b/src/windows/fuzzing/WslcCliArgumentFuzzing.cpp
@@ -0,0 +1,105 @@
+// Copyright (C) Microsoft Corporation. All rights reserved.
+
+// libFuzzer harness for the WSLC CLI argument parser (ParseArgumentsStateMachine).
+//
+// This target exercises the state machine that parses all `wslc.exe` command-line
+// arguments. It is fully standalone — no COM session or WSL service is required.
+
+#include "precomp.h"
+
+#include "arguments/Argument.h"
+#include "arguments/ArgumentParser.h"
+#include "arguments/ArgumentTypes.h"
+#include "core/Invocation.h"
+#include "FuzzingHarness.h"
+
+#include
+#include
+#include
+
+using namespace wsl::windows::wslc;
+using namespace wsl::windows::wslc::argument;
+
+// Split remaining fuzz bytes into null-delimited wide strings (argv-style).
+static std::vector SplitToArgs(FuzzInput& input)
+{
+ std::vector args;
+ while (!input.Empty())
+ {
+ auto s = input.ReadWideString(SIZE_MAX);
+ if (!s.empty())
+ {
+ args.push_back(std::move(s));
+ }
+ }
+ return args;
+}
+
+// Build a representative set of arguments covering all Kinds (Flag, Value, Positional, Forward).
+static std::vector GetFuzzArguments()
+{
+ return {
+ Argument::Create(ArgType::Help),
+ Argument::Create(ArgType::All),
+ Argument::Create(ArgType::Detach),
+ Argument::Create(ArgType::Force),
+ Argument::Create(ArgType::Interactive),
+ Argument::Create(ArgType::Quiet),
+ Argument::Create(ArgType::TTY),
+ Argument::Create(ArgType::Verbose),
+ Argument::Create(ArgType::Version),
+ Argument::Create(ArgType::Name),
+ Argument::Create(ArgType::Env, std::nullopt, NO_LIMIT),
+ Argument::Create(ArgType::Volume, std::nullopt, NO_LIMIT),
+ Argument::Create(ArgType::Publish, std::nullopt, NO_LIMIT),
+ Argument::Create(ArgType::WorkDir),
+ Argument::Create(ArgType::User),
+ Argument::Create(ArgType::Hostname),
+ Argument::Create(ArgType::Memory),
+ Argument::Create(ArgType::Signal),
+ Argument::Create(ArgType::Output),
+ Argument::Create(ArgType::ContainerId),
+ Argument::Create(ArgType::ImageId),
+ Argument::Create(ArgType::Command),
+ Argument::Create(ArgType::ForwardArgs),
+ };
+}
+
+extern "C" int LLVMFuzzerInitialize(int*, char***)
+{
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ if (size == 0 || size > 4096)
+ {
+ return -1;
+ }
+
+ try
+ {
+ FuzzInput input{data, size};
+ auto args = SplitToArgs(input);
+ if (args.empty())
+ {
+ return -1;
+ }
+
+ Invocation inv{std::move(args)};
+ ArgMap execArgs;
+ auto arguments = GetFuzzArguments();
+
+ ParseArgumentsStateMachine sm{inv, execArgs, std::move(arguments)};
+
+ while (sm.Step())
+ {
+ }
+ }
+ catch (...)
+ {
+ // Expected — most fuzzed inputs will fail. ASAN crashes bypass this.
+ }
+
+ return 0;
+}
diff --git a/src/windows/fuzzing/WslcSdkFuzzing.cpp b/src/windows/fuzzing/WslcSdkFuzzing.cpp
new file mode 100644
index 0000000000..a8a1f463f1
--- /dev/null
+++ b/src/windows/fuzzing/WslcSdkFuzzing.cpp
@@ -0,0 +1,111 @@
+// Copyright (C) Microsoft Corporation. All rights reserved.
+
+// libFuzzer harness for the WSLC SDK C API.
+//
+// Exercises the session and container settings flow with fuzz-controlled inputs.
+// Requires WSL service running on the host (installed via setup.ps1 on OneFuzz VMs).
+
+#include "precomp.h"
+
+#include "wslcsdk.h"
+
+#include "FuzzingHarness.h"
+
+#include
+#include
+#include
+
+extern "C" int LLVMFuzzerInitialize(int*, char***)
+{
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ if (size < 10 || size > 4096)
+ {
+ return -1;
+ }
+
+ FuzzInput input{data, size};
+
+ // Read session parameters from corpus
+ std::wstring sessionName = input.ReadWideString();
+ std::wstring storagePath = input.ReadWideString();
+
+ // Initialize session settings
+ WslcSessionSettings sessionSettings{};
+ HRESULT hr = WslcInitSessionSettings(sessionName.c_str(), storagePath.c_str(), &sessionSettings);
+ if (FAILED(hr))
+ {
+ return -1;
+ }
+
+ // Configure session with minimal resources
+ WslcSetSessionSettingsCpuCount(&sessionSettings, 1);
+ WslcSetSessionSettingsMemory(&sessionSettings, 512);
+ WslcSetSessionSettingsTimeout(&sessionSettings, 5000);
+
+ // Read container parameters
+ std::string imageName = input.ReadString();
+ std::string containerName = input.ReadString();
+ std::string hostName = input.ReadString();
+
+ // Read port mappings
+ uint8_t portCount = input.Read();
+ std::vector ports(portCount);
+ for (uint8_t i = 0; i < portCount; ++i)
+ {
+ ports[i].windowsPort = input.Read();
+ ports[i].containerPort = input.Read();
+ ports[i].protocol = static_cast(input.Read() % 2);
+ }
+
+ // Read flags
+ auto containerFlags = static_cast(input.Read());
+
+ // Attempt to create session — may fail if service isn't available
+ WslcSession session = nullptr;
+ hr = WslcCreateSession(&sessionSettings, &session, nullptr);
+ if (FAILED(hr) || !session)
+ {
+ return 0;
+ }
+
+ // Initialize container settings
+ WslcContainerSettings containerSettings{};
+ hr = WslcInitContainerSettings(imageName.c_str(), &containerSettings);
+ if (FAILED(hr))
+ {
+ WslcTerminateSession(session);
+ WslcReleaseSession(session);
+ return 0;
+ }
+
+ if (!containerName.empty())
+ {
+ WslcSetContainerSettingsName(&containerSettings, containerName.c_str());
+ }
+ if (!hostName.empty())
+ {
+ WslcSetContainerSettingsHostName(&containerSettings, hostName.c_str());
+ }
+ if (portCount > 0)
+ {
+ WslcSetContainerSettingsPortMappings(&containerSettings, ports.data(), portCount);
+ }
+ WslcSetContainerSettingsFlags(&containerSettings, containerFlags);
+
+ // Attempt container creation — exercises validation and service communication
+ WslcContainer container = nullptr;
+ hr = WslcCreateContainer(session, &containerSettings, &container, nullptr);
+ if (SUCCEEDED(hr) && container)
+ {
+ WslcReleaseContainer(container);
+ }
+
+ WslcTerminateSession(session);
+ WslcReleaseSession(session);
+
+ return 0;
+}
diff --git a/src/windows/fuzzing/WslcWinRtFuzzing.cpp b/src/windows/fuzzing/WslcWinRtFuzzing.cpp
new file mode 100644
index 0000000000..2efb446aba
--- /dev/null
+++ b/src/windows/fuzzing/WslcWinRtFuzzing.cpp
@@ -0,0 +1,98 @@
+// Copyright (C) Microsoft Corporation. All rights reserved.
+
+// libFuzzer harness for the WSLC SDK WinRT API.
+//
+// Exercises the WinRT projection of the session and container settings flow
+// with fuzz-controlled inputs. Requires WSL service running on the host.
+
+#include "precomp.h"
+
+#include
+#include
+#include
+#include
+
+#include "FuzzingHarness.h"
+
+#include
+#include
+
+using namespace winrt;
+using namespace winrt::Microsoft::WSL::Containers;
+using namespace winrt::Windows::Foundation;
+
+extern "C" int LLVMFuzzerInitialize(int*, char***)
+{
+ winrt::init_apartment();
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ if (size < 10 || size > 4096)
+ {
+ return -1;
+ }
+
+ FuzzInput input{data, size};
+
+ try
+ {
+ // Read session parameters from corpus
+ std::wstring sessionName = input.ReadWideString();
+ std::wstring storagePath = input.ReadWideString();
+
+ // Build session settings via WinRT
+ SessionSettings sessionSettings{hstring{sessionName}, hstring{storagePath}};
+ sessionSettings.CpuCount(1);
+ sessionSettings.MemoryMB(1024);
+ sessionSettings.Timeout(winrt::Windows::Foundation::TimeSpan{std::chrono::milliseconds{5000}});
+
+ // Attempt to create session
+ Session session{sessionSettings};
+ session.Start();
+
+ // Read container parameters from corpus
+ std::string imageName = input.ReadString();
+ ContainerSettings containerSettings{hstring{winrt::to_hstring(imageName)}};
+
+ std::string containerName = input.ReadString();
+ if (!containerName.empty())
+ {
+ containerSettings.Name(hstring{winrt::to_hstring(containerName)});
+ }
+
+ std::string hostName = input.ReadString();
+ if (!hostName.empty())
+ {
+ containerSettings.HostName(hstring{winrt::to_hstring(hostName)});
+ }
+
+ // Fuzz port mappings
+ uint8_t portCount = input.Read();
+ auto portMappings = containerSettings.PortMappings();
+ for (uint8_t i = 0; i < portCount; ++i)
+ {
+ uint16_t windowsPort = input.Read();
+ uint16_t containerPort = input.Read();
+ auto protocol = static_cast(input.Read() % 2);
+ portMappings.Append(ContainerPortMapping{windowsPort, containerPort, protocol});
+ }
+
+ // Fuzz flags
+ auto flags = static_cast(input.Read());
+ containerSettings.Flags(flags);
+
+ // Attempt container creation
+ auto container = session.CreateContainer(containerSettings);
+
+ // Cleanup
+ session.Terminate();
+ }
+ catch (...)
+ {
+ // Expected — most fuzzed inputs will fail. ASAN crashes bypass this.
+ }
+
+ return 0;
+}
diff --git a/src/windows/fuzzing/generate-seeds.ps1 b/src/windows/fuzzing/generate-seeds.ps1
new file mode 100644
index 0000000000..79f078cd2d
--- /dev/null
+++ b/src/windows/fuzzing/generate-seeds.ps1
@@ -0,0 +1,118 @@
+# Copyright (C) Microsoft Corporation. All rights reserved.
+#
+# Generates seed corpus files for each fuzzing harness.
+# Output is deterministic (no randomness) — run in the pipeline before staging.
+# Output goes into per-target subdirectories matching harness names.
+
+param(
+ [Parameter(Mandatory)]
+ [string]$OutputDir
+)
+
+# Write a wide (UTF-16LE) null-terminated string
+function Write-WideString([System.IO.BinaryWriter]$writer, [string]$s) {
+ $writer.Write([System.Text.Encoding]::Unicode.GetBytes($s + "`0"))
+}
+
+# Write a narrow (UTF-8) null-terminated string
+function Write-NarrowString([System.IO.BinaryWriter]$writer, [string]$s) {
+ $writer.Write([System.Text.Encoding]::UTF8.GetBytes($s + "`0"))
+}
+
+# Create a seed binary file, invoking $body with a BinaryWriter
+function New-SeedFile([string]$dir, [string]$file, [scriptblock]$body) {
+ New-Item -ItemType Directory -Path $dir -Force -ErrorAction Stop | Out-Null
+
+ # File::Create() resolves relative paths differently than PowerShell, so we use a full path.
+ $path = Join-Path (Resolve-Path $dir) $file
+
+ $stream = [System.IO.File]::Create($path)
+ $writer = [System.IO.BinaryWriter]::new($stream)
+ try {
+ & $body $writer
+ }
+ finally {
+ $writer.Close()
+ }
+}
+
+# --- CLI harness seeds ---
+# Format: concatenated null-terminated wide strings (argv-style, split by SplitToArgs)
+
+function New-CliSeed([string]$name, [string[]]$cliArgs) {
+ New-SeedFile "$OutputDir\WslcCliArgumentFuzzing\seeds" "$name.bin" {
+ param($writer)
+ foreach ($arg in $cliArgs) {
+ Write-WideString $writer $arg
+ }
+ }
+}
+
+New-CliSeed "install" @("--install", "Ubuntu")
+New-CliSeed "run-command" @("--name", "MyDistro", "--user", "root", "--", "ls", "-la")
+New-CliSeed "flags" @("--all", "--verbose", "--force")
+New-CliSeed "env-volume" @("--env", "FOO=bar", "--env", "BAZ=qux", "--volume", "C:\Users:/mnt/users", "--workdir", "/home/user")
+New-CliSeed "publish" @("--publish", "8080:80", "--publish", "443:443", "--hostname", "devbox")
+
+# --- SDK and WinRT harness seeds (same binary format) ---
+# Format: [widestr sessionName] [widestr storagePath]
+# [str imageName] [str containerName] [str hostName]
+# [u8 portCount] [portCount x (u16 windowsPort, u16 containerPort, u8 protocol)]
+# [u32 flags]
+
+$sdkSeedDir = "$OutputDir\WslcSdkFuzzing\seeds"
+
+function New-SdkSeed {
+ param(
+ [string]$Name,
+ [string]$SessionName,
+ [string]$StoragePath,
+ [string]$ImageName,
+ [string]$ContainerName,
+ [string]$HostName,
+ [hashtable[]]$Ports = @(),
+ [uint32]$Flags = 0
+ )
+ New-SeedFile $sdkSeedDir "$Name.bin" {
+ param($writer)
+ Write-WideString $writer $SessionName
+ Write-WideString $writer $StoragePath
+ Write-NarrowString $writer $ImageName
+ Write-NarrowString $writer $ContainerName
+ Write-NarrowString $writer $HostName
+ $writer.Write([byte]$Ports.Count)
+ foreach ($port in $Ports) {
+ $writer.Write([uint16]$port.WindowsPort)
+ $writer.Write([uint16]$port.ContainerPort)
+ $writer.Write([byte]$port.Protocol)
+ }
+ $writer.Write([uint32]$Flags)
+ }
+}
+
+New-SdkSeed -Name "basic-session" `
+ -SessionName "test-session" -StoragePath "C:\temp\storage" `
+ -ImageName "ubuntu:latest" -ContainerName "my-container" -HostName "localhost" `
+ -Ports @{WindowsPort=8080; ContainerPort=80; Protocol=0}, @{WindowsPort=443; ContainerPort=443; Protocol=1} `
+ -Flags 0
+
+New-SdkSeed -Name "empty-strings" `
+ -SessionName "" -StoragePath "" `
+ -ImageName "" -ContainerName "" -HostName ""
+
+New-SdkSeed -Name "long-names" `
+ -SessionName ("A" * 63) -StoragePath ("C:\very\long\path\" + ("x" * 40)) `
+ -ImageName ("registry.example.com/org/" + ("img" * 10) + ":v1.2.3") `
+ -ContainerName ("container-" + ("name" * 10)) -HostName ("host-" + ("name" * 10)) `
+ -Ports @{WindowsPort=9090; ContainerPort=9090; Protocol=0} `
+ -Flags ([uint32]::MaxValue)
+
+# WinRT uses the same seeds
+$winrtSeedDir = "$OutputDir\WslcWinRtFuzzing\seeds"
+New-Item -ItemType Directory -Path $winrtSeedDir -Force | Out-Null
+Copy-Item -Path "$sdkSeedDir\*" -Destination $winrtSeedDir -Force
+
+Write-Host "Seeds generated in: $OutputDir"
+Get-ChildItem -Recurse -File -Path $OutputDir | ForEach-Object {
+ Write-Host " $(Resolve-Path $_.FullName -Relative)"
+}
diff --git a/src/windows/fuzzing/setup.ps1 b/src/windows/fuzzing/setup.ps1
new file mode 100644
index 0000000000..c213a3c3fb
--- /dev/null
+++ b/src/windows/fuzzing/setup.ps1
@@ -0,0 +1,18 @@
+# OneFuzz VM setup for WSLC fuzzing.
+# Installs Hyper-V and WSL so the service is available for SDK harnesses.
+# RebootAfterSetup must be true in OneFuzzConfig.json for features to take effect.
+
+Set-Location -Path $PSScriptRoot
+$ErrorActionPreference = "Stop"
+
+Write-Host "Enabling Hyper-V..."
+Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All -NoRestart
+
+Write-Host "Enabling WSL..."
+Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -NoRestart
+
+$msiPath = Join-Path $PSScriptRoot "wsl.msi"
+Write-Host "Installing WSL from $msiPath..."
+Start-Process msiexec -ArgumentList "/i `"$msiPath`" /quiet /norestart" -Wait
+
+Write-Host "Setup complete. Reboot required."
diff --git a/src/windows/wslc/CMakeLists.txt b/src/windows/wslc/CMakeLists.txt
index d4a4ba6236..d448472d71 100644
--- a/src/windows/wslc/CMakeLists.txt
+++ b/src/windows/wslc/CMakeLists.txt
@@ -6,8 +6,11 @@ list(TRANSFORM WSLC_SUBDIR_PATHS APPEND /*.cpp OUTPUT_VARIABLE SOURCE_PATTERNS)
file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS ${HEADER_PATTERNS})
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS ${SOURCE_PATTERNS})
+set(WSLC_MAIN ${CMAKE_CURRENT_SOURCE_DIR}/core/Main.cpp)
+list(REMOVE_ITEM SOURCES ${WSLC_MAIN})
+
# Object library for WSLC components.
-# Used to build the executable and also unit testing components.
+# Used by the wslc executable, unit tests, and fuzzing harnesses.
add_library(wslclib OBJECT ${SOURCES} ${HEADERS})
target_include_directories(wslclib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${WSLC_SUBDIR_PATHS})
@@ -18,18 +21,17 @@ target_link_libraries(wslclib
advapi32
crypt32)
-
target_precompile_headers(wslclib REUSE_FROM common)
set_target_properties(wslclib PROPERTIES FOLDER windows)
# Create wslc.exe
# N.B. Linking wslclib (OBJECT library) brings both object files and
# transitive dependencies. Do not also use $.
-add_executable(wslc)
+add_executable(wslc ${WSLC_MAIN})
target_link_libraries(wslc wslclib)
+target_precompile_headers(wslc REUSE_FROM common)
set_target_properties(wslc PROPERTIES FOLDER windows)
-# For prettier source tree browsing
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES} ${HEADERS})
diff --git a/src/windows/wslinstall/CMakeLists.txt b/src/windows/wslinstall/CMakeLists.txt
index 4f9fcf4399..1aca465e30 100644
--- a/src/windows/wslinstall/CMakeLists.txt
+++ b/src/windows/wslinstall/CMakeLists.txt
@@ -2,16 +2,26 @@ set(SOURCES
DllMain.cpp
version.rc)
+if (WSL_BUILD_FUZZING)
+ # wslinstall is an MSI custom action DLL loaded from a temp directory at install time,
+ # where the dynamic ASAN runtime can't be resolved. Build it without ASAN so it has no
+ # dependency on the runtime. This also requires linking a non-instrumented common library.
+ string(REPLACE "/fsanitize=address" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+ set(WSLINSTALL_COMMON common_noasan)
+else()
+ set(WSLINSTALL_COMMON common)
+endif()
+
add_library(wslinstall SHARED ${SOURCES} wslinstall.def)
target_include_directories(wslinstall PUBLIC ${CMAKE_CURRENT_LIST_DIR})
set_target_properties(wslinstall PROPERTIES FOLDER windows)
-target_precompile_headers(wslinstall REUSE_FROM common)
+target_precompile_headers(wslinstall REUSE_FROM ${WSLINSTALL_COMMON})
target_link_libraries(wslinstall
${COMMON_LINK_LIBRARIES}
${MSI_LINK_LIBRARIES}
- common
+ ${WSLINSTALL_COMMON}
legacy_stdio_definitions
Crypt32.lib
sfc.lib)
\ No newline at end of file