From 556207bc536107196df2f7f11f61ee26ba180fcd Mon Sep 17 00:00:00 2001 From: Dustin Ingram Date: Wed, 29 Apr 2026 15:38:06 -0400 Subject: [PATCH 1/4] Add a 'disable_remediation_pre_review' configuration field (#15436) This PR adds an optional field `disable_remediation_pre_review` which allows projects to opt out of human pre-review for proposed code changes when automated remediation is enabled. --- docs/getting-started/new_project_guide.md | 8 ++++++++ infra/presubmit.py | 1 + 2 files changed, 9 insertions(+) diff --git a/docs/getting-started/new_project_guide.md b/docs/getting-started/new_project_guide.md index 7ffa69a033bb..248f791c2a23 100644 --- a/docs/getting-started/new_project_guide.md +++ b/docs/getting-started/new_project_guide.md @@ -87,6 +87,7 @@ This configuration file stores project metadata. The following attributes are su - [builds_per_day](#build_frequency) (optional) - [file_github_issue](#file_github_issue) (optional) - [disable_remediation](#disable_remediation) (optional) +- [disable_remediation_pre_review](#disable_remediation_pre_review) (optional) ### homepage You project's homepage. @@ -218,6 +219,13 @@ changes. If enabled (default), proposed code changes and comments to remediate bugs may be automatically included in disclosure that is private during the embargo of each issue on a case-by-case basis basis. +### disable_remediation_pre_review (optional) {#disable_remediation_pre_review} +Opt out of human-in-the-loop pre-review for proposed remediation code changes. +If this is enabled, proposed code changes will be included in disclosure +notifications without prior manual review by the OSS-Fuzz team. If disabled +(default), all proposed remediation changes will be reviewed by a human before +being shared. + ## Dockerfile {#dockerfile} This configuration file defines the Docker image for your project. Your [build.sh](#buildsh) script will be executed in inside the container you define. diff --git a/infra/presubmit.py b/infra/presubmit.py index edf448b5ed41..9d2538a2b424 100755 --- a/infra/presubmit.py +++ b/infra/presubmit.py @@ -91,6 +91,7 @@ class ProjectYamlChecker: 'coverage_extra_args', 'disabled', 'disable_remediation', + 'disable_remediation_pre_review', 'fuzzing_engines', 'help_url', 'homepage', From 450149896f0b164428d3a8a358125938a72df195 Mon Sep 17 00:00:00 2001 From: DavidKorczynski Date: Wed, 29 Apr 2026 20:42:43 +0100 Subject: [PATCH 2/4] angle: extend project (#15435) Signed-off-by: David Korczynski --- projects/angle/Dockerfile | 8 +- projects/angle/build.sh | 25 ++- projects/angle/fuzz_preprocessor.cc | 89 ++++++++ projects/angle/fuzz_sha1.cc | 4 +- projects/angle/fuzz_spirv_parser.cc | 113 ++++++++++ projects/angle/fuzz_spirv_transform.cc | 80 +++++++ projects/angle/fuzz_translator.cc | 288 +++++++++++++++++++++++++ projects/angle/fuzzer_profile | 126 ++++++++++- projects/angle/gen_clones.py | 62 ++++++ projects/angle/glsl.dict | 223 +++++++++++++++++++ 10 files changed, 1001 insertions(+), 17 deletions(-) create mode 100644 projects/angle/fuzz_preprocessor.cc create mode 100644 projects/angle/fuzz_spirv_parser.cc create mode 100644 projects/angle/fuzz_spirv_transform.cc create mode 100644 projects/angle/fuzz_translator.cc create mode 100644 projects/angle/gen_clones.py create mode 100644 projects/angle/glsl.dict diff --git a/projects/angle/Dockerfile b/projects/angle/Dockerfile index 046c85c0a563..8a8dd36e12cb 100644 --- a/projects/angle/Dockerfile +++ b/projects/angle/Dockerfile @@ -19,14 +19,16 @@ RUN apt-get update && \ apt-get install --no-install-recommends -y \ lsb-release sudo pkg-config file -RUN git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git +RUN git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git ENV PATH=/src/depot_tools:$PATH WORKDIR angle -RUN fetch angle +# Increased retries and added more robust fetch/sync +RUN for i in {1..20}; do fetch --nohooks angle && break || (echo "Fetch failed, retrying in 60s..." && sleep 60 && if [ $i -eq 20 ]; then exit 1; fi); done +RUN for i in {1..30}; do gclient sync --jobs 1 --no-history --shallow && break || (echo "Sync failed, retrying in 60s..." && sleep 60 && if [ $i -eq 30 ]; then exit 1; fi); done RUN ./build/install-build-deps.sh --no-prompt COPY build.sh *.cc fuzzer_profile $SRC/ # Add fuzzer profile -RUN cat $SRC/fuzzer_profile >> $SRC/angle/BUILD.gn +RUN cat $SRC/fuzzer_profile >> /src/angle/BUILD.gn diff --git a/projects/angle/build.sh b/projects/angle/build.sh index 29cadaf72e38..33cf8beeea87 100644 --- a/projects/angle/build.sh +++ b/projects/angle/build.sh @@ -22,10 +22,25 @@ if [ "$SANITIZER" = "coverage" ] || [ "$SANITIZER" = "introspector" ] || [ "$SAN export SANITIZER="address" fi +# Hack to fix clang version mismatch +# The build system somehow thinks clang version is 23, but it's 22 in /usr/local +if [ ! -d /usr/local/lib/clang/23 ]; then + ln -s /usr/local/lib/clang/22 /usr/local/lib/clang/23 || true +fi + +# Remove flags not supported by OSS-Fuzz's clang version +sed -i '/-fdiagnostics-show-inlining-chain/d' build/config/compiler/BUILD.gn +sed -i '/-fno-lifetime-dse/d' build/config/compiler/BUILD.gn +sed -i '/-Wa,--crel,--allow-experimental-crel/d' build/config/compiler/BUILD.gn +# Also handle the ubsan ignore flags in sanitizers.gni +sed -i '/-fsanitize-ignore-for-ubsan-feature=${invoker.sanitizer}/d' build/config/sanitizers/sanitizers.gni + # Configure arguments for gn build ARGS="treat_warnings_as_errors=false is_component_build=false libcxx_is_shared=false is_debug=false" -ARGS+=" use_custom_libcxx=false use_sysroot=true ozone_platform_x11=false" +ARGS+=" use_custom_libcxx=true use_sysroot=true ozone_platform_x11=false" ARGS+=" is_clang=true clang_use_chrome_plugins=false clang_base_path=\"/usr/local\"" +# Enable MSL and WGSL translators specifically, without enabling the full Metal renderer +ARGS+=" angle_enable_msl=true angle_enable_wgpu=true" # Configure arguments for gn build if [ "$SANITIZER" = "undefined" ]; then @@ -38,14 +53,20 @@ cp $SRC/*.cc src/fuzz/ # Generate ninja file for build gn gen out/fuzz --args="$ARGS" -echo $SANITIZER + # Build binary autoninja -C out/fuzz fuzz_sha1 autoninja -C out/fuzz fuzz_translator +autoninja -C out/fuzz fuzz_spirv_transform +autoninja -C out/fuzz fuzz_spirv_parser +autoninja -C out/fuzz fuzz_preprocessor # Copy binary to $OUT cp ./out/fuzz/fuzz_sha1 $OUT cp ./out/fuzz/fuzz_translator $OUT +cp ./out/fuzz/fuzz_spirv_transform $OUT +cp ./out/fuzz/fuzz_spirv_parser $OUT +cp ./out/fuzz/fuzz_preprocessor $OUT # Reset sanitizer if [ -n "$ORIGINAL_SANITIZER" ]; then diff --git a/projects/angle/fuzz_preprocessor.cc b/projects/angle/fuzz_preprocessor.cc new file mode 100644 index 000000000000..169c57596313 --- /dev/null +++ b/projects/angle/fuzz_preprocessor.cc @@ -0,0 +1,89 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "compiler/preprocessor/Preprocessor.h" +#include "compiler/preprocessor/DiagnosticsBase.h" +#include "compiler/preprocessor/DirectiveHandlerBase.h" +#include "compiler/preprocessor/Token.h" + +using namespace angle::pp; + +namespace +{ + +class DoNothingDiagnostics : public Diagnostics +{ + public: + void print(ID id, const SourceLocation &loc, const std::string &text) override {} +}; + +class DoNothingDirectiveHandler : public DirectiveHandler +{ + public: + void handleError(const SourceLocation &loc, const std::string &msg) override {} + void handlePragma(const SourceLocation &loc, + const std::string &name, + const std::string &value, + bool stdgl) override {} + void handleExtension(const SourceLocation &loc, + const std::string &name, + const std::string &behavior) override {} + void handleVersion(const SourceLocation &loc, int version, ShShaderSpec spec, MacroSet *macro_set) override {} +}; + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < 1) return 0; + + FuzzedDataProvider fuzzedData(data, size); + + ShShaderSpec spec = static_cast(fuzzedData.ConsumeIntegralInRange(0, 5)); // SH_GLES3_2_SPEC is 5 + WebGLExtensionDisableBehavior behavior = fuzzedData.ConsumeBool() ? WebGLExtensionDisableBehavior::Standard : WebGLExtensionDisableBehavior::AnywhereInShader; + + PreprocessorSettings settings(spec, behavior); + settings.maxMacroExpansionDepth = fuzzedData.ConsumeIntegralInRange(0, 1000); + + DoNothingDiagnostics diagnostics; + DoNothingDirectiveHandler directiveHandler; + Preprocessor preprocessor(&diagnostics, &directiveHandler, settings); + + std::vector remainingData = fuzzedData.ConsumeRemainingBytes(); + if (remainingData.empty()) return 0; + + // Ensure null termination + remainingData.push_back(0); + + const char *strings[] = { reinterpret_cast(remainingData.data()) }; + if (!preprocessor.init(1, strings, nullptr)) + { + return 0; + } + + Token token; + // Lex up to 1000 tokens to avoid infinite loops or very long execution + for (int i = 0; i < 1000; ++i) + { + preprocessor.lex(&token); + if (token.type == Token::LAST) break; + } + + return 0; +} diff --git a/projects/angle/fuzz_sha1.cc b/projects/angle/fuzz_sha1.cc index 6b87935057cf..3fd884aa23cc 100644 --- a/projects/angle/fuzz_sha1.cc +++ b/projects/angle/fuzz_sha1.cc @@ -20,10 +20,10 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - if (size <= 32 || size > 1024) return 0; - angle::base::SecureHashAlgorithm sha; sha.Update(data, size); + sha.Final(); + (void)sha.Digest(); return 0; } diff --git a/projects/angle/fuzz_spirv_parser.cc b/projects/angle/fuzz_spirv_parser.cc new file mode 100644 index 000000000000..66cd1d8a942a --- /dev/null +++ b/projects/angle/fuzz_spirv_parser.cc @@ -0,0 +1,113 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +// fuzz_spirv_parser.cc: A libfuzzer fuzzer for the autogenerated SPIR-V instruction parser. + +#include +#include +#include + +#include "common/spirv/spirv_instruction_parser_autogen.h" +#include "common/spirv/spirv_types.h" + +using namespace angle::spirv; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + // SPIR-V instructions are at least one word (4 bytes) + if (size < 4) + { + return 0; + } + + // Ensure size is a multiple of 4 + size_t numWords = size / 4; + const uint32_t *instructions = reinterpret_cast(data); + + size_t currentWord = 0; + + // Skip SPIR-V header if it looks like one (optional but helps hitting the parser) + if (numWords > kHeaderIndexInstructions && instructions[kHeaderIndexMagic] == spv::MagicNumber) + { + currentWord = kHeaderIndexInstructions; + } + + while (currentWord < numWords) + { + const uint32_t *instruction = &instructions[currentWord]; + spv::Op opCode; + uint32_t wordCount; + + GetInstructionOpAndLength(instruction, &opCode, &wordCount); + + if (wordCount == 0 || currentWord + wordCount > numWords) + { + break; + } + + // We can't easily call all Parse* functions because they require specific arguments. + // But GetInstructionOpAndLength already covers some logic. + // We can try to call some common ones if the opCode matches. + + switch (opCode) + { + case spv::OpName: + { + IdRef target; + LiteralString name; + ParseName(instruction, &target, &name); + break; + } + case spv::OpTypeVoid: + { + IdResult id; + ParseTypeVoid(instruction, &id); + break; + } + case spv::OpTypeFloat: + { + IdResult id; + LiteralInteger width; + spv::FPEncoding encoding; + ParseTypeFloat(instruction, &id, &width, &encoding); + break; + } + // Add more common ones to increase coverage + case spv::OpTypeInt: + { + IdResult id; + LiteralInteger width; + LiteralInteger signedness; + ParseTypeInt(instruction, &id, &width, &signedness); + break; + } + case spv::OpTypeVector: + { + IdResult id; + IdRef componentType; + LiteralInteger componentCount; + ParseTypeVector(instruction, &id, &componentType, &componentCount); + break; + } + default: + break; + } + + currentWord += wordCount; + } + + return 0; +} diff --git a/projects/angle/fuzz_spirv_transform.cc b/projects/angle/fuzz_spirv_transform.cc new file mode 100644 index 000000000000..3837cea24006 --- /dev/null +++ b/projects/angle/fuzz_spirv_transform.cc @@ -0,0 +1,80 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// fuzz_spirv_transform.cc: A libfuzzer fuzzer for SPIR-V transformations in the Vulkan backend. + +#include +#include + +#include "common/spirv/spirv_types.h" +#include "libANGLE/renderer/vulkan/spv_utils.h" +#include "libANGLE/renderer/vulkan/ShaderInterfaceVariableInfoMap.h" + +using namespace rx; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < 10) return 0; + + FuzzedDataProvider fuzzedData(data, size); + + SpvTransformOptions options; + options.shaderType = static_cast(fuzzedData.ConsumeIntegralInRange(0, static_cast(gl::ShaderType::EnumCount) - 1)); + options.isLastPreFragmentStage = fuzzedData.ConsumeBool(); + options.isTransformFeedbackStage = fuzzedData.ConsumeBool(); + options.isTransformFeedbackEmulated = fuzzedData.ConsumeBool(); + options.isMultisampledFramebufferFetch = fuzzedData.ConsumeBool(); + options.enableSampleShading = fuzzedData.ConsumeBool(); + options.validate = fuzzedData.ConsumeBool(); + options.useSpirvVaryingPrecisionFixer = fuzzedData.ConsumeBool(); + options.removeDepthStencilInput = fuzzedData.ConsumeBool(); + + ShaderInterfaceVariableInfoMap variableInfoMap; + + // Add some random variable info + uint32_t numVars = fuzzedData.ConsumeIntegralInRange(0, 10); + for (uint32_t i = 0; i < numVars; ++i) + { + uint32_t id = fuzzedData.ConsumeIntegralInRange(1, 100); + ShaderInterfaceVariableInfo &info = variableInfoMap.add(options.shaderType, id); + info.descriptorSet = fuzzedData.ConsumeIntegralInRange(0, 3); + info.binding = fuzzedData.ConsumeIntegralInRange(0, 16); + info.location = fuzzedData.ConsumeIntegralInRange(0, 16); + info.component = fuzzedData.ConsumeIntegralInRange(0, 3); + } + + // The remaining data is the SPIR-V blob + std::vector remainingData = fuzzedData.ConsumeRemainingBytes(); + if (remainingData.size() % 4 != 0) + { + remainingData.resize(remainingData.size() - (remainingData.size() % 4)); + } + + if (remainingData.empty()) return 0; + + angle::spirv::Blob initialSpirvBlob; + for (size_t i = 0; i < remainingData.size(); i += 4) + { + uint32_t word; + memcpy(&word, &remainingData[i], 4); + initialSpirvBlob.push_back(word); + } + + angle::spirv::Blob spirvBlobOut; + // We don't care about the result, just want to see if it crashes. + SpvTransformSpirvCode(options, variableInfoMap, initialSpirvBlob, &spirvBlobOut); + + return 0; +} diff --git a/projects/angle/fuzz_translator.cc b/projects/angle/fuzz_translator.cc new file mode 100644 index 000000000000..7475dfac5308 --- /dev/null +++ b/projects/angle/fuzz_translator.cc @@ -0,0 +1,288 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +// fuzz_translator.cc: Improved libfuzzer fuzzer for the shader translator. + +#ifdef UNSAFE_BUFFERS_BUILD +# pragma allow_unsafe_buffers +#endif + +#include +#include +#include +#include +#include +#include + +#include "angle_gl.h" +#include "anglebase/no_destructor.h" +#include "common/hash_containers.h" +#include "compiler/translator/Compiler.h" +#include "compiler/translator/util.h" + +using namespace sh; + +namespace +{ +struct TranslatorCacheKey +{ + bool operator==(const TranslatorCacheKey &other) const + { + return type == other.type && spec == other.spec && output == other.output; + } + + uint32_t type = 0; + uint32_t spec = 0; + uint32_t output = 0; +}; +} // anonymous namespace + +namespace std +{ + +template <> +struct hash +{ + std::size_t operator()(const TranslatorCacheKey &k) const + { + return (hash()(k.type) << 1) ^ (hash()(k.spec) >> 1) ^ + hash()(k.output); + } +}; +} // namespace std + +struct TCompilerDeleter +{ + void operator()(TCompiler *compiler) const { DeleteCompiler(compiler); } +}; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ShaderDumpHeader header{}; + if (size <= sizeof(header)) + { + return 0; + } + + // Make sure the rest of data will be a valid C string so that we don't have to copy it. + if (data[size - 1] != 0) + { + return 0; + } + + memcpy(&header, data, sizeof(header)); + ShCompileOptions options{}; + + // Safety check for offsetof and sizes to avoid buffer overflow if ShCompileOptions changes. + size_t basicSize = offsetof(ShCompileOptions, metal); + if (basicSize > sizeof(header.basicCompileOptions)) basicSize = sizeof(header.basicCompileOptions); + memcpy(&options, &header.basicCompileOptions, basicSize); + + memcpy(&options.metal, &header.metalCompileOptions, std::min(sizeof(options.metal), sizeof(header.metalCompileOptions))); + memcpy(&options.pls, &header.plsCompileOptions, std::min(sizeof(options.pls), sizeof(header.plsCompileOptions))); + + size -= sizeof(header); + data += sizeof(header); + uint32_t type = header.type; + uint32_t spec = header.spec; + + // Supported shader types + if (type != GL_FRAGMENT_SHADER && type != GL_VERTEX_SHADER && + type != GL_COMPUTE_SHADER && type != GL_GEOMETRY_SHADER && + type != GL_TESS_CONTROL_SHADER && type != GL_TESS_EVALUATION_SHADER) + { + return 0; + } + + // Supported specs + if (spec > SH_GLES3_2_SPEC) + { + return 0; + } + + ShShaderOutput shaderOutput = static_cast(header.output); + + // Validation of output format + switch (shaderOutput) + { + case SH_ESSL_OUTPUT: + case SH_GLSL_150_CORE_OUTPUT: + case SH_GLSL_330_CORE_OUTPUT: + case SH_GLSL_400_CORE_OUTPUT: + case SH_GLSL_410_CORE_OUTPUT: + case SH_GLSL_420_CORE_OUTPUT: + case SH_GLSL_430_CORE_OUTPUT: + case SH_GLSL_440_CORE_OUTPUT: + case SH_GLSL_450_CORE_OUTPUT: + case SH_SPIRV_VULKAN_OUTPUT: + case SH_HLSL_3_0_OUTPUT: + case SH_HLSL_4_1_OUTPUT: + case SH_MSL_METAL_OUTPUT: + case SH_WGSL_OUTPUT: + break; + default: + return 0; + } + + bool hasUnsupportedOptions = false; + + const bool hasMacGLSLOptions = options.addAndTrueToLoopCondition || + options.unfoldShortCircuit || options.rewriteRowMajorMatrices; + + if (!IsOutputGLSL(shaderOutput) && !IsOutputESSL(shaderOutput)) + { + hasUnsupportedOptions = + hasUnsupportedOptions || options.emulateAtan2FloatFunction || options.clampFragDepth || + options.regenerateStructNames || options.rewriteRepeatedAssignToSwizzled || + options.useUnusedStandardSharedBlocks || options.selectViewInNvGLSLVertexShader; + + hasUnsupportedOptions = hasUnsupportedOptions || hasMacGLSLOptions; + } + else + { +#if !defined(ANGLE_PLATFORM_APPLE) + hasUnsupportedOptions = hasUnsupportedOptions || hasMacGLSLOptions; +#endif + } + if (!IsOutputESSL(shaderOutput)) + { + hasUnsupportedOptions = hasUnsupportedOptions || options.skipAllValidationAndTransforms; + } + if (!IsOutputSPIRV(shaderOutput)) + { + hasUnsupportedOptions = hasUnsupportedOptions || options.addVulkanXfbEmulationSupportCode || + options.roundOutputAfterDithering || + options.addAdvancedBlendEquationsEmulation; + } + if (!IsOutputHLSL(shaderOutput)) + { + hasUnsupportedOptions = hasUnsupportedOptions || + options.expandSelectHLSLIntegerPowExpressions || + options.allowTranslateUniformBlockToStructuredBuffer || + options.rewriteIntegerUnaryMinusOperator; + } + if (!IsOutputMSL(shaderOutput)) + { + hasUnsupportedOptions = hasUnsupportedOptions || options.ensureLoopForwardProgress; + } + + // If there are any options not supported with this output, don't attempt to run the translator. + if (hasUnsupportedOptions) + { + return 0; + } + + // Make sure the rest of the options are in a valid range. + options.pls.fragmentSyncType = static_cast( + static_cast(options.pls.fragmentSyncType) % + static_cast(ShFragmentSynchronizationType::InvalidEnum)); + + // Force enable options that are required by the output generators. + if (IsOutputSPIRV(shaderOutput)) + { + options.removeInactiveVariables = true; + options.retainInactiveFragmentOutputs = false; + } + if (IsOutputMSL(shaderOutput)) + { + options.removeInactiveVariables = true; + options.retainInactiveFragmentOutputs = true; + } + + if (!sh::Initialize()) + { + return 0; + } + + TranslatorCacheKey key; + key.type = type; + key.spec = spec; + key.output = shaderOutput; + + using UniqueTCompiler = std::unique_ptr; + static angle::base::NoDestructor> + translators; + + if (translators->find(key) == translators->end()) + { + UniqueTCompiler translator( + ConstructCompiler(type, static_cast(spec), shaderOutput)); + + if (translator == nullptr) + { + return 0; + } + + ShBuiltInResources resources; + sh::InitBuiltInResources(&resources); + + // Enable all the extensions to have more coverage + resources.OES_standard_derivatives = 1; + resources.OES_EGL_image_external = 1; + resources.OES_EGL_image_external_essl3 = 1; + resources.NV_EGL_stream_consumer_external = 1; + resources.ARB_texture_rectangle = 1; + resources.EXT_blend_func_extended = 1; + resources.EXT_conservative_depth = 1; + resources.EXT_draw_buffers = 1; + resources.EXT_frag_depth = 1; + resources.EXT_shader_texture_lod = 1; + resources.EXT_shader_framebuffer_fetch = 1; + resources.ARM_shader_framebuffer_fetch = 1; + resources.ARM_shader_framebuffer_fetch_depth_stencil = 1; + resources.EXT_YUV_target = 1; + resources.APPLE_clip_distance = 1; + resources.MaxDualSourceDrawBuffers = 1; + resources.EXT_gpu_shader5 = 1; + resources.MaxClipDistances = 1; + resources.EXT_shadow_samplers = 1; + resources.EXT_clip_cull_distance = 1; + resources.ANGLE_clip_cull_distance = 1; + resources.EXT_primitive_bounding_box = 1; + resources.OES_primitive_bounding_box = 1; + resources.OES_texture_3D = 1; + resources.OES_texture_storage_multisample_2d_array = 1; + resources.OES_shader_io_blocks = 1; + resources.EXT_shader_io_blocks = 1; + resources.OES_tessellation_shader = 1; + resources.EXT_tessellation_shader = 1; + resources.EXT_geometry_shader = 1; + resources.OES_geometry_shader = 1; + resources.OES_gpu_shader5 = 1; + resources.NV_shader_noperspective_interpolation = 1; + + if (!translator->Init(resources)) + { + return 0; + } + + (*translators)[key] = std::move(translator); + } + + auto &translator = (*translators)[key]; + + // Enable options that any security-sensitive application should enable + options.limitExpressionComplexity = true; + options.limitCallStackDepth = true; + options.rejectWebglShadersWithLargeVariables = true; + options.rejectWebglShadersWithUndefinedBehavior = true; + + const char *shaderStrings[] = {reinterpret_cast(data)}; + + translator->compile(shaderStrings, options); + + return 0; +} diff --git a/projects/angle/fuzzer_profile b/projects/angle/fuzzer_profile index e6d0d8ee6e28..d0e31f1e2ee6 100644 --- a/projects/angle/fuzzer_profile +++ b/projects/angle/fuzzer_profile @@ -17,7 +17,7 @@ if (angle_has_build) { "-g", "-fprofile-instr-generate", "-fcoverage-mapping", - "-resource-dir=/usr/local/lib/clang/22", + "-resource-dir=/usr/local/lib/clang/23", "-fcoverage-prefix-map=../../src/=/src/angle/src/", "-fcoverage-prefix-map=../../include/=/src/angle/include/", "-fdebug-compilation-dir=/src/angle", @@ -39,21 +39,17 @@ if (angle_has_build) { ] } else if (getenv("FUZZING_ENGINE") == "libfuzzer") { cflags += [ "-fsanitize=fuzzer-no-link" ] - ldflags += [ getenv("LIB_FUZZING_ENGINE_DEPRECATED") ] + ldflags += [ getenv("LIB_FUZZING_ENGINE") ] } else { cflags += [ "-fsanitize=fuzzer-no-link" ] ldflags += [ getenv("LIB_FUZZING_ENGINE") ] } configs -= ["//build/config/compiler:thin_archive"] - - libs = [ - "/usr/local/lib/clang/22/lib/x86_64-unknown-linux-gnu/libclang_rt.fuzzer.a", - ] } executable("fuzz_translator") { - sources = [ "src/compiler/fuzz/translator_fuzzer.cpp" ] + sources = [ "src/fuzz/fuzz_translator.cc" ] include_dirs = [ "include", @@ -62,6 +58,44 @@ if (angle_has_build) { deps = [ ":translator", + ":angle_common", + ] + + cflags = [ + "-fsanitize=" + getenv("SANITIZER"), + "-O1", + "-fno-omit-frame-pointer", + "-g", + "-fprofile-instr-generate", + "-fcoverage-mapping", + "-fsanitize=fuzzer-no-link", + "-resource-dir=/usr/local/lib/clang/23", + "-UANGLE_USE_ABSEIL", + "-fcoverage-prefix-map=../../src/=/src/angle/src/", + "-fcoverage-prefix-map=../../include/=/src/angle/include/", + "-fdebug-compilation-dir=/src/angle", + ] + + ldflags = [ + "-fsanitize=" + getenv("SANITIZER"), + "-fprofile-instr-generate", + getenv("LIB_FUZZING_ENGINE"), + ] + + configs -= ["//build/config/compiler:thin_archive"] + } + + executable("fuzz_spirv_transform") { + sources = [ "src/fuzz/fuzz_spirv_transform.cc" ] + + include_dirs = [ + "include", + "src", + ] + + deps = [ + ":angle_static", + ":angle_common", ] cflags = [ @@ -72,7 +106,7 @@ if (angle_has_build) { "-fprofile-instr-generate", "-fcoverage-mapping", "-fsanitize=fuzzer-no-link", - "-resource-dir=/usr/local/lib/clang/22", + "-resource-dir=/usr/local/lib/clang/23", "-UANGLE_USE_ABSEIL", "-fcoverage-prefix-map=../../src/=/src/angle/src/", "-fcoverage-prefix-map=../../include/=/src/angle/include/", @@ -82,12 +116,84 @@ if (angle_has_build) { ldflags = [ "-fsanitize=" + getenv("SANITIZER"), "-fprofile-instr-generate", + getenv("LIB_FUZZING_ENGINE"), ] configs -= ["//build/config/compiler:thin_archive"] + } - libs = [ - "/usr/local/lib/clang/22/lib/x86_64-unknown-linux-gnu/libclang_rt.fuzzer.a", + executable("fuzz_spirv_parser") { + sources = [ "src/fuzz/fuzz_spirv_parser.cc" ] + + include_dirs = [ + "include", + "src", + "third_party/spirv-headers/src/include", + ] + + deps = [ + "src/common/spirv:angle_spirv_parser", + ":angle_common", ] + + cflags = [ + "-fsanitize=" + getenv("SANITIZER"), + "-O1", + "-fno-omit-frame-pointer", + "-g", + "-fprofile-instr-generate", + "-fcoverage-mapping", + "-fsanitize=fuzzer-no-link", + "-resource-dir=/usr/local/lib/clang/23", + "-UANGLE_USE_ABSEIL", + "-fcoverage-prefix-map=../../src/=/src/angle/src/", + "-fcoverage-prefix-map=../../include/=/src/angle/include/", + "-fdebug-compilation-dir=/src/angle", + ] + + ldflags = [ + "-fsanitize=" + getenv("SANITIZER"), + "-fprofile-instr-generate", + getenv("LIB_FUZZING_ENGINE"), + ] + + configs -= ["//build/config/compiler:thin_archive"] + } + + executable("fuzz_preprocessor") { + sources = [ "src/fuzz/fuzz_preprocessor.cc" ] + + include_dirs = [ + "include", + "src", + ] + + deps = [ + ":preprocessor", + ":angle_common", + ] + + cflags = [ + "-fsanitize=" + getenv("SANITIZER"), + "-O1", + "-fno-omit-frame-pointer", + "-g", + "-fprofile-instr-generate", + "-fcoverage-mapping", + "-fsanitize=fuzzer-no-link", + "-resource-dir=/usr/local/lib/clang/23", + "-UANGLE_USE_ABSEIL", + "-fcoverage-prefix-map=../../src/=/src/angle/src/", + "-fcoverage-prefix-map=../../include/=/src/angle/include/", + "-fdebug-compilation-dir=/src/angle", + ] + + ldflags = [ + "-fsanitize=" + getenv("SANITIZER"), + "-fprofile-instr-generate", + getenv("LIB_FUZZING_ENGINE"), + ] + + configs -= ["//build/config/compiler:thin_archive"] } } diff --git a/projects/angle/gen_clones.py b/projects/angle/gen_clones.py new file mode 100644 index 000000000000..794168809a80 --- /dev/null +++ b/projects/angle/gen_clones.py @@ -0,0 +1,62 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +import os +import re + +def parse_deps(deps_file): + with open(deps_file, 'r') as f: + content = f.read() + + # Very basic parser for DEPS file + # Look for 'path': { 'url': 'url@rev' } + deps = {} + + # First, get variables + vars = {} + vars_match = re.search(r'vars = \{(.*?)\}', content, re.DOTALL) + if vars_match: + vars_content = vars_match.group(1) + for var_match in re.finditer(r"'(.*?)':\s*'(.*?)'", vars_content): + vars[var_match.group(1)] = var_match.group(2) + + # Then, get deps + deps_match = re.search(r'deps = \{(.*?)\n\}', content, re.DOTALL) + if deps_match: + deps_content = deps_match.group(1) + # Match 'path': { 'url': ... } or 'path': 'url' + for dep_match in re.finditer(r"'(.*?)':\s*\{(.*?)\}", deps_content, re.DOTALL): + path = dep_match.group(1) + inner = dep_match.group(2) + url_match = re.search(r"'url':\s*(.*?)(,|$)", inner) + if url_match: + url_expr = url_match.group(1).strip() + # Resolve Var() and strings + url = url_expr.replace("Var('", "").replace("')", "").replace("'", "").replace('"', '').replace(' + ', '') + for k, v in vars.items(): + url = url.replace(k, v) + deps[path] = url + + return deps + +if __name__ == "__main__": + deps = parse_deps('projects/angle/angle-src/DEPS') + for path, url in deps.items(): + if '@' in url: + base_url, rev = url.split('@') + print(f"RUN git clone {base_url} /src/angle/{path} && cd /src/angle/{path} && git checkout {rev}") + else: + print(f"RUN git clone --depth 1 {url} /src/angle/{path}") diff --git a/projects/angle/glsl.dict b/projects/angle/glsl.dict new file mode 100644 index 000000000000..2b3ec2d09dbf --- /dev/null +++ b/projects/angle/glsl.dict @@ -0,0 +1,223 @@ +# GLSL Dictionary +"attribute" +"const" +"uniform" +"varying" +"layout" +"centroid" +"flat" +"smooth" +"noperspective" +"patch" +"sample" +"break" +"continue" +"do" +"for" +"while" +"switch" +"case" +"default" +"if" +"else" +"in" +"out" +"inout" +"float" +"double" +"int" +"void" +"bool" +"true" +"false" +"invariant" +"precise" +"discard" +"return" +"mat2" +"mat3" +"mat4" +"dmat2" +"dmat3" +"dmat4" +"mat2x2" +"mat2x3" +"mat2x4" +"dmat2x2" +"dmat2x3" +"dmat2x4" +"mat3x2" +"mat3x3" +"mat3x4" +"dmat3x2" +"dmat3x3" +"dmat3x4" +"mat4x2" +"mat4x3" +"mat4x4" +"dmat4x2" +"dmat4x3" +"dmat4x4" +"vec2" +"vec3" +"vec4" +"ivec2" +"ivec3" +"ivec4" +"bvec2" +"bvec3" +"bvec4" +"dvec2" +"dvec3" +"dvec4" +"uint" +"uvec2" +"uvec3" +"uvec4" +"lowp" +"mediump" +"highp" +"precision" +"sampler1D" +"sampler2D" +"sampler3D" +"samplerCube" +"sampler1DShadow" +"sampler2DShadow" +"samplerCubeShadow" +"sampler1DArray" +"sampler2DArray" +"sampler1DArrayShadow" +"sampler2DArrayShadow" +"isampler1D" +"isampler2D" +"isampler3D" +"isamplerCube" +"isampler1DArray" +"isampler2DArray" +"usampler1D" +"usampler2D" +"usampler3D" +"usamplerCube" +"usampler1DArray" +"usampler2DArray" +"sampler2DRect" +"sampler2DRectShadow" +"isampler2DRect" +"usampler2DRect" +"samplerBuffer" +"isamplerBuffer" +"usamplerBuffer" +"sampler2DMS" +"isampler2DMS" +"usampler2DMS" +"sampler2DMSArray" +"isampler2DMSArray" +"usampler2DMSArray" +"samplerCubeArray" +"samplerCubeArrayShadow" +"isamplerCubeArray" +"usamplerCubeArray" +"image1D" +"iimage1D" +"uimage1D" +"image2D" +"iimage2D" +"uimage2D" +"image3D" +"iimage3D" +"uimage3D" +"image2DRect" +"iimage2DRect" +"uimage2DRect" +"imageCube" +"iimageCube" +"uimageCube" +"imageBuffer" +"iimageBuffer" +"uimageBuffer" +"image1DArray" +"iimage1DArray" +"uimage1DArray" +"image2DArray" +"iimage2DArray" +"uimage2DArray" +"imageCubeArray" +"iimageCubeArray" +"uimageCubeArray" +"image2DMS" +"iimage2DMS" +"uimage2DMS" +"image2DMSArray" +"iimage2DMSArray" +"uimage2DMSArray" +"struct" +"gl_Position" +"gl_PointSize" +"gl_ClipDistance" +"gl_CullDistance" +"gl_VertexID" +"gl_InstanceID" +"gl_FragCoord" +"gl_FrontFacing" +"gl_PointCoord" +"gl_SampleID" +"gl_SamplePosition" +"gl_SampleMaskIn" +"gl_FragDepth" +"gl_SampleMask" +"gl_NumWorkGroups" +"gl_WorkGroupSize" +"gl_WorkGroupID" +"gl_LocalInvocationID" +"gl_GlobalInvocationID" +"gl_LocalInvocationIndex" +"gl_MaxVertexAttribs" +"gl_MaxVertexUniformComponents" +"gl_MaxVaryingComponents" +"gl_MaxCombinedTextureImageUnits" +"gl_MaxCombinedLODBias" +"gl_MaxTextureImageUnits" +"gl_MaxFragmentUniformComponents" +"gl_MaxDrawBuffers" +"gl_MaxClipDistances" +"gl_MaxGeometryInputComponents" +"gl_MaxGeometryOutputComponents" +"gl_MaxGeometryTextureImageUnits" +"gl_MaxGeometryOutputVertices" +"gl_MaxGeometryTotalOutputComponents" +"gl_MaxGeometryUniformComponents" +"gl_MaxGeometryVaryingComponents" +"gl_MaxTessControlInputComponents" +"gl_MaxTessControlOutputComponents" +"gl_MaxTessControlTextureImageUnits" +"gl_MaxTessControlUniformComponents" +"gl_MaxTessControlTotalOutputComponents" +"gl_MaxTessEvaluationInputComponents" +"gl_MaxTessEvaluationOutputComponents" +"gl_MaxTessEvaluationTextureImageUnits" +"gl_MaxTessEvaluationUniformComponents" +"gl_MaxTessPatchComponents" +"gl_MaxPatchVertices" +"gl_MaxTessGenLevel" +"gl_MaxViewports" +"gl_MaxVertexUniformVectors" +"gl_MaxVaryingVectors" +"gl_MaxFragmentUniformVectors" +"gl_MaxCombinedShaderStorageBlocks" +"gl_MaxCombinedAtomicCounterBuffers" +"gl_MaxCombinedAtomicCounters" +"gl_MaxCombinedImageUniforms" +"gl_MaxComputeWorkGroupCount" +"gl_MaxComputeWorkGroupSize" +"gl_MaxComputeUniformComponents" +"gl_MaxComputeTextureImageUnits" +"gl_MaxComputeImageUniforms" +"gl_MaxComputeAtomicCounterBuffers" +"gl_MaxComputeAtomicCounters" +"gl_MaxComputeMemberOffsets" +"gl_MaxComputeShaderStorageBlocks" +"gl_MaxComputeUniformBlocks" +"gl_MaxComputeTextureImageUnits" +"gl_MaxComputeWorkGroupSize" +"gl_MaxComputeWorkGroupCount" From 0319c10ecefa0279727548a8e348ae9d7ab5cf07 Mon Sep 17 00:00:00 2001 From: DavidKorczynski Date: Wed, 29 Apr 2026 20:43:13 +0100 Subject: [PATCH 3/4] libsodium: extend project (#15433) Signed-off-by: David Korczynski --- projects/libsodium/crypto_aead_fuzzer.cc | 170 ++++++++++++++++++ projects/libsodium/crypto_box_fuzzer.cc | 59 ++++++ .../libsodium/crypto_generichash_fuzzer.cc | 62 +++++++ projects/libsodium/crypto_kdf_fuzzer.cc | 50 ++++++ projects/libsodium/crypto_kx_fuzzer.cc | 59 ++++++ projects/libsodium/crypto_sign_fuzzer.cc | 68 +++++++ 6 files changed, 468 insertions(+) create mode 100644 projects/libsodium/crypto_aead_fuzzer.cc create mode 100644 projects/libsodium/crypto_box_fuzzer.cc create mode 100644 projects/libsodium/crypto_generichash_fuzzer.cc create mode 100644 projects/libsodium/crypto_kdf_fuzzer.cc create mode 100644 projects/libsodium/crypto_kx_fuzzer.cc create mode 100644 projects/libsodium/crypto_sign_fuzzer.cc diff --git a/projects/libsodium/crypto_aead_fuzzer.cc b/projects/libsodium/crypto_aead_fuzzer.cc new file mode 100644 index 000000000000..354f9f1bc35c --- /dev/null +++ b/projects/libsodium/crypto_aead_fuzzer.cc @@ -0,0 +1,170 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "fake_random.h" + +typedef int (*aead_encrypt_fn)(unsigned char *cipher, + unsigned long long *cipher_len, + const unsigned char *message, + unsigned long long message_len, + const unsigned char *ad, + unsigned long long ad_len, + const unsigned char *nsec, + const unsigned char *npub, + const unsigned char *k); + +typedef int (*aead_decrypt_fn)(unsigned char *message, + unsigned long long *message_len, + unsigned char *nsec, + const unsigned char *cipher, + unsigned long long cipher_len, + const unsigned char *ad, + unsigned long long ad_len, + const unsigned char *npub, + const unsigned char *k); + +struct AEAD_Algorithm { + aead_encrypt_fn encrypt; + aead_decrypt_fn decrypt; + size_t key_bytes; + size_t npub_bytes; + size_t a_bytes; + int (*is_available)(void); +}; + +static int always_available(void) { return 1; } + +extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size) { + if (sodium_init() == -1) { + return 0; + } + + if (size < 2) { + return 0; + } + + static AEAD_Algorithm algs[] = { + { + crypto_aead_chacha20poly1305_ietf_encrypt, + crypto_aead_chacha20poly1305_ietf_decrypt, + crypto_aead_chacha20poly1305_ietf_KEYBYTES, + crypto_aead_chacha20poly1305_ietf_NPUBBYTES, + crypto_aead_chacha20poly1305_ietf_ABYTES, + always_available + }, + { + crypto_aead_xchacha20poly1305_ietf_encrypt, + crypto_aead_xchacha20poly1305_ietf_decrypt, + crypto_aead_xchacha20poly1305_ietf_KEYBYTES, + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, + crypto_aead_xchacha20poly1305_ietf_ABYTES, + always_available + }, + { + crypto_aead_chacha20poly1305_encrypt, + crypto_aead_chacha20poly1305_decrypt, + crypto_aead_chacha20poly1305_KEYBYTES, + crypto_aead_chacha20poly1305_NPUBBYTES, + crypto_aead_chacha20poly1305_ABYTES, + always_available + }, +#ifdef crypto_aead_aegis128l_KEYBYTES + { + crypto_aead_aegis128l_encrypt, + crypto_aead_aegis128l_decrypt, + crypto_aead_aegis128l_KEYBYTES, + crypto_aead_aegis128l_NPUBBYTES, + crypto_aead_aegis128l_ABYTES, + always_available + }, +#endif +#ifdef crypto_aead_aegis256_KEYBYTES + { + crypto_aead_aegis256_encrypt, + crypto_aead_aegis256_decrypt, + crypto_aead_aegis256_KEYBYTES, + crypto_aead_aegis256_NPUBBYTES, + crypto_aead_aegis256_ABYTES, + always_available + }, +#endif + { + crypto_aead_aes256gcm_encrypt, + crypto_aead_aes256gcm_decrypt, + crypto_aead_aes256gcm_KEYBYTES, + crypto_aead_aes256gcm_NPUBBYTES, + crypto_aead_aes256gcm_ABYTES, + crypto_aead_aes256gcm_is_available + } + }; + size_t num_algs = sizeof(algs) / sizeof(algs[0]); + + uint8_t choice = data[0] % num_algs; + const AEAD_Algorithm &alg = algs[choice]; + + if (alg.is_available && !alg.is_available()) { + return 0; + } + + if (size < 1 + alg.key_bytes + alg.npub_bytes) { + return 0; + } + + const unsigned char *k = data + 1; + const unsigned char *npub = data + 1 + alg.key_bytes; + const unsigned char *msg = data + 1 + alg.key_bytes + alg.npub_bytes; + size_t total_msg_len = size - (1 + alg.key_bytes + alg.npub_bytes); + + // Split remaining data into message and associated data + size_t ad_len = total_msg_len / 4; + size_t msg_len = total_msg_len - ad_len; + const unsigned char *ad = msg; + msg += ad_len; + + // Limit lengths to avoid timeouts + if (msg_len > 4096) msg_len = 4096; + if (ad_len > 4096) ad_len = 4096; + + unsigned char *ciphertext = (unsigned char *) malloc(msg_len + alg.a_bytes); + unsigned long long ciphertext_len; + + alg.encrypt(ciphertext, &ciphertext_len, + msg, msg_len, + ad, ad_len, + NULL, npub, k); + + unsigned char *decrypted = (unsigned char *) malloc(msg_len + alg.a_bytes); + unsigned long long decrypted_len; + int err = alg.decrypt(decrypted, &decrypted_len, + NULL, + ciphertext, ciphertext_len, + ad, ad_len, + npub, k); + + if (err == 0) { + assert(decrypted_len == msg_len); + assert(memcmp(decrypted, msg, msg_len) == 0); + } + + free(ciphertext); + free(decrypted); + + return 0; +} diff --git a/projects/libsodium/crypto_box_fuzzer.cc b/projects/libsodium/crypto_box_fuzzer.cc new file mode 100644 index 000000000000..92046eb39394 --- /dev/null +++ b/projects/libsodium/crypto_box_fuzzer.cc @@ -0,0 +1,59 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "fake_random.h" + +extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size) { + int initialized = sodium_init(); + assert(initialized >= 0); + + if (size < crypto_box_SEEDBYTES + crypto_box_NONCEBYTES) { + return 0; + } + + setup_fake_random(data, size); + + unsigned char pk1[crypto_box_PUBLICKEYBYTES]; + unsigned char sk1[crypto_box_SECRETKEYBYTES]; + unsigned char pk2[crypto_box_PUBLICKEYBYTES]; + unsigned char sk2[crypto_box_SECRETKEYBYTES]; + + const unsigned char *seed1 = data; + const unsigned char *nonce = data + crypto_box_SEEDBYTES; + const unsigned char *msg = nonce + crypto_box_NONCEBYTES; + size_t msg_len = size - (crypto_box_SEEDBYTES + crypto_box_NONCEBYTES); + + // Generate keypairs. Using seed for the first one to be more deterministic from input. + crypto_box_seed_keypair(pk1, sk1, seed1); + // Second keypair can be generated normally, but randombytes is hooked so it's also deterministic. + crypto_box_keypair(pk2, sk2); + + unsigned char *ciphertext = (unsigned char *) malloc(msg_len + crypto_box_MACBYTES); + int err = crypto_box_easy(ciphertext, msg, msg_len, nonce, pk2, sk1); + assert(err == 0); + + unsigned char *decrypted = (unsigned char *) malloc(msg_len); + err = crypto_box_open_easy(decrypted, ciphertext, msg_len + crypto_box_MACBYTES, nonce, pk1, sk2); + assert(err == 0); + assert(memcmp(decrypted, msg, msg_len) == 0); + + free(ciphertext); + free(decrypted); + + return 0; +} diff --git a/projects/libsodium/crypto_generichash_fuzzer.cc b/projects/libsodium/crypto_generichash_fuzzer.cc new file mode 100644 index 000000000000..7bd0b431e5ff --- /dev/null +++ b/projects/libsodium/crypto_generichash_fuzzer.cc @@ -0,0 +1,62 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (sodium_init() == -1) { + return 0; + } + + // We need some data for outlen, keylen, and the message itself + if (size < 2) { + return 0; + } + + size_t outlen = (data[0] % (crypto_generichash_BYTES_MAX - crypto_generichash_BYTES_MIN + 1)) + crypto_generichash_BYTES_MIN; + size_t keylen = (data[1] % (crypto_generichash_KEYBYTES_MAX - crypto_generichash_KEYBYTES_MIN + 1)); + + if (keylen > 0 && size < 2 + keylen) { + keylen = 0; + } + + const uint8_t *key = keylen > 0 ? data + 2 : NULL; + const uint8_t *msg = data + 2 + keylen; + size_t msglen = size - (2 + keylen); + + unsigned char out[crypto_generichash_BYTES_MAX]; + + // Single-part API + crypto_generichash(out, outlen, msg, msglen, key, keylen); + + // Multi-part API + crypto_generichash_state state; + if (crypto_generichash_init(&state, key, keylen, outlen) == 0) { + // Split msg into two parts if possible + if (msglen > 0) { + size_t part1 = msglen / 2; + crypto_generichash_update(&state, msg, part1); + crypto_generichash_update(&state, msg + part1, msglen - part1); + } else { + crypto_generichash_update(&state, msg, 0); + } + crypto_generichash_final(&state, out, outlen); + } + + return 0; +} diff --git a/projects/libsodium/crypto_kdf_fuzzer.cc b/projects/libsodium/crypto_kdf_fuzzer.cc new file mode 100644 index 000000000000..cc617a2f3d37 --- /dev/null +++ b/projects/libsodium/crypto_kdf_fuzzer.cc @@ -0,0 +1,50 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (sodium_init() == -1) { + return 0; + } + + if (size < crypto_kdf_KEYBYTES + 8 + 8) { + return 0; + } + + const uint8_t *key = data; + uint64_t subkey_id; + memcpy(&subkey_id, data + crypto_kdf_KEYBYTES, 8); + + char ctx[crypto_kdf_CONTEXTBYTES]; + memcpy(ctx, data + crypto_kdf_KEYBYTES + 8, crypto_kdf_CONTEXTBYTES > (size - (crypto_kdf_KEYBYTES + 8)) ? (size - (crypto_kdf_KEYBYTES + 8)) : crypto_kdf_CONTEXTBYTES); + if (crypto_kdf_CONTEXTBYTES > (size - (crypto_kdf_KEYBYTES + 8))) { + memset(ctx + (size - (crypto_kdf_KEYBYTES + 8)), 0, crypto_kdf_CONTEXTBYTES - (size - (crypto_kdf_KEYBYTES + 8))); + } + + // subkey length can be between crypto_kdf_BYTES_MIN and crypto_kdf_BYTES_MAX + size_t subkey_len = crypto_kdf_BYTES_MIN + (data[size-1] % (crypto_kdf_BYTES_MAX - crypto_kdf_BYTES_MIN + 1)); + + unsigned char *subkey = (unsigned char *)malloc(subkey_len); + + crypto_kdf_derive_from_key(subkey, subkey_len, subkey_id, ctx, key); + + free(subkey); + + return 0; +} diff --git a/projects/libsodium/crypto_kx_fuzzer.cc b/projects/libsodium/crypto_kx_fuzzer.cc new file mode 100644 index 000000000000..e814d8c00668 --- /dev/null +++ b/projects/libsodium/crypto_kx_fuzzer.cc @@ -0,0 +1,59 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (sodium_init() == -1) { + return 0; + } + + if (size < crypto_kx_SEEDBYTES + crypto_kx_PUBLICKEYBYTES) { + return 0; + } + + unsigned char pk[crypto_kx_PUBLICKEYBYTES]; + unsigned char sk[crypto_kx_SECRETKEYBYTES]; + unsigned char seed[crypto_kx_SEEDBYTES]; + + memcpy(seed, data, crypto_kx_SEEDBYTES); + + // Test keypair generation from seed + crypto_kx_seed_keypair(pk, sk, seed); + + unsigned char client_pk[crypto_kx_PUBLICKEYBYTES]; + unsigned char client_sk[crypto_kx_SECRETKEYBYTES]; + unsigned char server_pk[crypto_kx_PUBLICKEYBYTES]; + unsigned char server_sk[crypto_kx_SECRETKEYBYTES]; + + // Use data to simulate other party's public key + memcpy(server_pk, data + size - crypto_kx_PUBLICKEYBYTES, crypto_kx_PUBLICKEYBYTES); + + unsigned char rx[crypto_kx_SESSIONKEYBYTES]; + unsigned char tx[crypto_kx_SESSIONKEYBYTES]; + + // Test client session keys + crypto_kx_client_session_keys(rx, tx, pk, sk, server_pk); + + // Test server session keys + // We'll use the same 'pk' and 'sk' as server keys now + memcpy(client_pk, data + size - crypto_kx_PUBLICKEYBYTES, crypto_kx_PUBLICKEYBYTES); + crypto_kx_server_session_keys(rx, tx, pk, sk, client_pk); + + return 0; +} diff --git a/projects/libsodium/crypto_sign_fuzzer.cc b/projects/libsodium/crypto_sign_fuzzer.cc new file mode 100644 index 000000000000..8116c0edd579 --- /dev/null +++ b/projects/libsodium/crypto_sign_fuzzer.cc @@ -0,0 +1,68 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "fake_random.h" + +extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size) { + int initialized = sodium_init(); + assert(initialized >= 0); + + if (size < crypto_sign_SEEDBYTES) { + return 0; + } + + setup_fake_random(data, size); + + unsigned char pk[crypto_sign_PUBLICKEYBYTES]; + unsigned char sk[crypto_sign_SECRETKEYBYTES]; + + const unsigned char *seed = data; + const unsigned char *msg = data + crypto_sign_SEEDBYTES; + size_t msg_len = size - crypto_sign_SEEDBYTES; + + crypto_sign_seed_keypair(pk, sk, seed); + + unsigned char *sig = (unsigned char *) malloc(crypto_sign_BYTES); + unsigned long long sig_len; + int err = crypto_sign_detached(sig, &sig_len, msg, msg_len, sk); + assert(err == 0); + assert(sig_len == crypto_sign_BYTES); + + err = crypto_sign_verify_detached(sig, msg, msg_len, pk); + assert(err == 0); + + // Test multi-part signature + crypto_sign_state state; + crypto_sign_init(&state); + crypto_sign_update(&state, msg, msg_len / 2); + crypto_sign_update(&state, msg + msg_len / 2, msg_len - msg_len / 2); + unsigned char sig2[crypto_sign_BYTES]; + err = crypto_sign_final_create(&state, sig2, &sig_len, sk); + assert(err == 0); + + // For verification, we need a new state or re-initialized state + crypto_sign_init(&state); + crypto_sign_update(&state, msg, msg_len / 2); + crypto_sign_update(&state, msg + msg_len / 2, msg_len - msg_len / 2); + err = crypto_sign_final_verify(&state, sig2, pk); + assert(err == 0); + + free(sig); + + return 0; +} From e0a7fa9a25c92c1a5c3f43d471b107c3ebe31cec Mon Sep 17 00:00:00 2001 From: DavidKorczynski Date: Wed, 29 Apr 2026 20:43:33 +0100 Subject: [PATCH 4/4] eigen: add additional harnesses (#15431) Signed-off-by: David Korczynski --- projects/eigen/dense_solver_fuzzer.cc | 196 ++++++++++++++++++++++++++ projects/eigen/geometry_fuzzer.cc | 92 ++++++++++++ projects/eigen/sparse_fuzzer.cc | 88 ++++++++++++ projects/eigen/tensor_fuzzer.cc | 85 +++++++++++ 4 files changed, 461 insertions(+) create mode 100644 projects/eigen/dense_solver_fuzzer.cc create mode 100644 projects/eigen/geometry_fuzzer.cc create mode 100644 projects/eigen/sparse_fuzzer.cc create mode 100644 projects/eigen/tensor_fuzzer.cc diff --git a/projects/eigen/dense_solver_fuzzer.cc b/projects/eigen/dense_solver_fuzzer.cc new file mode 100644 index 000000000000..6668573104b8 --- /dev/null +++ b/projects/eigen/dense_solver_fuzzer.cc @@ -0,0 +1,196 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +static constexpr Eigen::Index kEigenTestMaxSize = 32; + +template +void fuzzQR(FuzzedDataProvider* stream) { + Eigen::Index rows = stream->ConsumeIntegralInRange(1, kEigenTestMaxSize); + Eigen::Index cols = stream->ConsumeIntegralInRange(1, kEigenTestMaxSize); + + Eigen::Matrix m(rows, cols); + for (Eigen::Index i = 0; i < m.size(); ++i) { + m(i) = stream->ConsumeFloatingPoint(); + } + + // HouseholderQR + Eigen::HouseholderQR> qr(m); + (void)qr.householderQ(); + (void)qr.matrixQR().template triangularView(); + + // ColPivHouseholderQR + Eigen::ColPivHouseholderQR> cpqr(m); + (void)cpqr.rank(); + (void)cpqr.isInvertible(); + + // FullPivHouseholderQR + Eigen::FullPivHouseholderQR> fpqr(m); + (void)fpqr.rank(); + (void)fpqr.isInvertible(); +} + +template +void fuzzLU(FuzzedDataProvider* stream) { + Eigen::Index size = stream->ConsumeIntegralInRange(1, kEigenTestMaxSize); + Eigen::Matrix m(size, size); + for (Eigen::Index i = 0; i < m.size(); ++i) { + m(i) = stream->ConsumeFloatingPoint(); + } + + // PartialPivLU + Eigen::PartialPivLU> plu(m); + (void)plu.determinant(); + (void)plu.inverse(); + + // FullPivLU + Eigen::FullPivLU> flu(m); + (void)flu.determinant(); + (void)flu.inverse(); + (void)flu.rank(); +} + +template +void fuzzCholesky(FuzzedDataProvider* stream) { + Eigen::Index size = stream->ConsumeIntegralInRange(1, kEigenTestMaxSize); + Eigen::Matrix m(size, size); + for (Eigen::Index i = 0; i < m.size(); ++i) { + m(i) = stream->ConsumeFloatingPoint(); + } + + // LLT + Eigen::LLT> llt(m); + if (llt.info() == Eigen::Success) { + (void)llt.matrixL(); + (void)llt.matrixU(); + Eigen::Matrix b = Eigen::Matrix::Random(size); + (void)llt.solve(b); + } + + // LDLT + Eigen::LDLT> ldlt(m); + if (ldlt.info() == Eigen::Success) { + (void)ldlt.matrixL(); + (void)ldlt.matrixU(); + (void)ldlt.vectorD(); + (void)ldlt.isPositive(); + (void)ldlt.isNegative(); + Eigen::Matrix b = Eigen::Matrix::Random(size); + (void)ldlt.solve(b); + } +} + +template +void fuzzEigenvalues(FuzzedDataProvider* stream) { + Eigen::Index size = stream->ConsumeIntegralInRange(1, kEigenTestMaxSize); + Eigen::Matrix m(size, size); + for (Eigen::Index i = 0; i < m.size(); ++i) { + m(i) = stream->ConsumeFloatingPoint(); + } + + // EigenSolver + Eigen::EigenSolver> es(m); + if (es.info() == Eigen::Success) { + (void)es.eigenvalues(); + (void)es.eigenvectors(); + } + + // SelfAdjointEigenSolver (on a symmetric matrix) + Eigen::Matrix symm = m + m.transpose(); + Eigen::SelfAdjointEigenSolver> saes(symm); + if (saes.info() == Eigen::Success) { + (void)saes.eigenvalues(); + (void)saes.eigenvectors(); + } +} + +template +void fuzzSVD(FuzzedDataProvider* stream) { + Eigen::Index rows = stream->ConsumeIntegralInRange(1, kEigenTestMaxSize); + Eigen::Index cols = stream->ConsumeIntegralInRange(1, kEigenTestMaxSize); + Eigen::Matrix m(rows, cols); + for (Eigen::Index i = 0; i < m.size(); ++i) { + m(i) = stream->ConsumeFloatingPoint(); + } + + // JacobiSVD + Eigen::JacobiSVD> svd(m, Eigen::ComputeThinU | Eigen::ComputeThinV); + if (svd.info() == Eigen::Success) { + (void)svd.singularValues(); + (void)svd.matrixU(); + (void)svd.matrixV(); + } + + // BDCSVD + Eigen::BDCSVD> bdcsvd(m, Eigen::ComputeThinU | Eigen::ComputeThinV); + if (bdcsvd.info() == Eigen::Success) { + (void)bdcsvd.singularValues(); + (void)bdcsvd.matrixU(); + (void)bdcsvd.matrixV(); + } +} + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider stream(data, size); + + uint8_t type = stream.ConsumeIntegral(); + // 0: QR, 1: QR, 2: LU, 3: LU, + // 4: Cholesky, 5: Cholesky, 6: Eigenvalues, 7: Eigenvalues, + // 8: SVD, 9: SVD + switch (type % 10) { + case 0: + fuzzQR(&stream); + break; + case 1: + fuzzQR(&stream); + break; + case 2: + fuzzLU(&stream); + break; + case 3: + fuzzLU(&stream); + break; + case 4: + fuzzCholesky(&stream); + break; + case 5: + fuzzCholesky(&stream); + break; + case 6: + fuzzEigenvalues(&stream); + break; + case 7: + fuzzEigenvalues(&stream); + break; + case 8: + fuzzSVD(&stream); + break; + case 9: + fuzzSVD(&stream); + break; + } + + return 0; +} diff --git a/projects/eigen/geometry_fuzzer.cc b/projects/eigen/geometry_fuzzer.cc new file mode 100644 index 000000000000..7b0cc45c84c3 --- /dev/null +++ b/projects/eigen/geometry_fuzzer.cc @@ -0,0 +1,92 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +namespace { + +template +void fuzzGeometry(FuzzedDataProvider* stream) { + typedef Eigen::Matrix Vector3; + typedef Eigen::Quaternion Quaternion; + typedef Eigen::AngleAxis AngleAxis; + typedef Eigen::Rotation2D Rotation2D; + typedef Eigen::Transform Transform3; + + Vector3 v1(stream->ConsumeFloatingPoint(), + stream->ConsumeFloatingPoint(), + stream->ConsumeFloatingPoint()); + Vector3 v2(stream->ConsumeFloatingPoint(), + stream->ConsumeFloatingPoint(), + stream->ConsumeFloatingPoint()); + + // Quaternions + Quaternion q1(stream->ConsumeFloatingPoint(), + stream->ConsumeFloatingPoint(), + stream->ConsumeFloatingPoint(), + stream->ConsumeFloatingPoint()); + q1.normalize(); + Quaternion q2(stream->ConsumeFloatingPoint(), + stream->ConsumeFloatingPoint(), + stream->ConsumeFloatingPoint(), + stream->ConsumeFloatingPoint()); + q2.normalize(); + + (void)q1.slerp(stream->ConsumeFloatingPoint(), q2); + (void)q1.inverse(); + (void)q1.conjugate(); + (void)(q1 * q2); + (void)(q1 * v1); + + // AngleAxis + AngleAxis aa1(stream->ConsumeFloatingPoint(), v1.normalized()); + (void)aa1.toRotationMatrix(); + (void)Quaternion(aa1); + + // Transform + Transform3 t1 = Transform3::Identity(); + t1.translate(v1); + t1.rotate(q1); + t1.scale(stream->ConsumeFloatingPoint()); + + (void)(t1 * v2); + (void)t1.inverse(); + + // Hyperplane + Eigen::Hyperplane hp(v1.normalized(), stream->ConsumeFloatingPoint()); + (void)hp.absDistance(v2); + (void)hp.projection(v2); + + // ParametrizedLine + Eigen::ParametrizedLine line(v1, v2.normalized()); + (void)line.distance(v2); + (void)line.projection(v2); +} + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider stream(data, size); + + uint8_t type = stream.ConsumeIntegral(); + if (type % 2 == 0) { + fuzzGeometry(&stream); + } else { + fuzzGeometry(&stream); + } + + return 0; +} diff --git a/projects/eigen/sparse_fuzzer.cc b/projects/eigen/sparse_fuzzer.cc new file mode 100644 index 000000000000..8e68c664be18 --- /dev/null +++ b/projects/eigen/sparse_fuzzer.cc @@ -0,0 +1,88 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +namespace { + +static constexpr Eigen::Index kEigenTestMaxSize = 16; + +template +void fuzzSparse(FuzzedDataProvider* stream) { + Eigen::Index rows = stream->ConsumeIntegralInRange(1, kEigenTestMaxSize); + Eigen::Index cols = stream->ConsumeIntegralInRange(1, kEigenTestMaxSize); + size_t numTriplets = stream->ConsumeIntegralInRange(0, rows * cols / 10 + 1); + + typedef Eigen::Triplet T; + std::vector triplets; + for (size_t i = 0; i < numTriplets; ++i) { + Eigen::Index r = stream->ConsumeIntegralInRange(0, rows - 1); + Eigen::Index c = stream->ConsumeIntegralInRange(0, cols - 1); + Scalar v; + if constexpr (std::is_integral_v) { + v = stream->ConsumeIntegral(); + } else { + v = stream->ConsumeFloatingPoint(); + } + triplets.push_back(T(r, c, v)); + } + + Eigen::SparseMatrix mat(rows, cols); + mat.setFromTriplets(triplets.begin(), triplets.end()); + + // Basic operations + (void)mat.transpose(); + (void)mat.adjoint(); + (void)mat.norm(); + + if (rows == cols) { + // SparseLU + Eigen::SparseLU> lu; + lu.compute(mat); + if (lu.info() == Eigen::Success) { + Eigen::Matrix b = Eigen::Matrix::Random(rows); + (void)lu.solve(b); + } + } + + // SparseQR + Eigen::SparseQR, Eigen::COLAMDOrdering> qr; + qr.compute(mat); + if (qr.info() == Eigen::Success) { + Eigen::Matrix b = Eigen::Matrix::Random(rows); + (void)qr.solve(b); + } +} + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider stream(data, size); + + uint8_t type = stream.ConsumeIntegral(); + switch (type % 2) { + case 0: + fuzzSparse(&stream); + break; + case 1: + fuzzSparse(&stream); + break; + } + + return 0; +} diff --git a/projects/eigen/tensor_fuzzer.cc b/projects/eigen/tensor_fuzzer.cc new file mode 100644 index 000000000000..2c10381cac1d --- /dev/null +++ b/projects/eigen/tensor_fuzzer.cc @@ -0,0 +1,85 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +namespace { + +static constexpr int kMaxDimensions = 5; +static constexpr int kMaxDimSize = 10; + +template +void fuzzTensor(FuzzedDataProvider* stream) { + int numDims = stream->ConsumeIntegralInRange(1, kMaxDimensions); + Eigen::array dims; + for (int i = 0; i < kMaxDimensions; ++i) { + if (i < numDims) { + dims[i] = stream->ConsumeIntegralInRange(1, kMaxDimSize); + } else { + dims[i] = 1; + } + } + + // We'll use a fixed rank for simplicity in templating, but varied sizes. + Eigen::Tensor tensor( + stream->ConsumeIntegralInRange(1, kMaxDimSize), + stream->ConsumeIntegralInRange(1, kMaxDimSize), + stream->ConsumeIntegralInRange(1, kMaxDimSize)); + + for (Eigen::Index i = 0; i < tensor.size(); ++i) { + if constexpr (std::is_integral_v) { + tensor(i) = stream->ConsumeIntegral(); + } else { + tensor(i) = stream->ConsumeFloatingPoint(); + } + } + + // Basic operations + (void)tensor.maximum(); + (void)tensor.minimum(); + (void)tensor.sum(); + (void)tensor.mean(); + + // Chipping + if (tensor.dimension(0) > 0) { + (void)tensor.chip(0, 0); + } + + // Shuffling + Eigen::array shuffle_dims = {1, 0, 2}; + (void)tensor.shuffle(shuffle_dims); + + // Striding + Eigen::array strides = {2, 1, 1}; + (void)tensor.stride(strides); +} + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider stream(data, size); + + uint8_t type = stream.ConsumeIntegral(); + switch (type % 2) { + case 0: + fuzzTensor(&stream); + break; + case 1: + fuzzTensor(&stream); + break; + } + + return 0; +}