Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions tensorflow_serving/util/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,31 @@ cc_library(
name = "oss_or_google",
hdrs = ["oss_or_google.h"],
)
# Add this block to tensorflow_serving/util/BUILD
# after the existing json_tensor cc_library target.
#
# This defines the OSS-Fuzz fuzz target for the JSON tensor parser.
# It is built only when compiled with --config=oss-fuzz (libFuzzer + ASan).

load("@rules_cc//cc:cc_binary.bzl", "cc_binary")

# Fuzz target for OSS-Fuzz continuous fuzzing.
# Build with: bazel build --config=oss-fuzz //tensorflow_serving/util:json_tensor_fuzzer
cc_binary(
name = "json_tensor_fuzzer",
srcs = ["json_tensor_fuzzer.cc"],
copts = ["-fsanitize=fuzzer-no-link"],
linkopts = ["-fsanitize=fuzzer"],
deps = [
":json_tensor",
"//tensorflow_serving/apis:classification_cc_proto",
"//tensorflow_serving/apis:predict_cc_proto",
"//tensorflow_serving/apis:regression_cc_proto",
"@com_google_absl//absl/strings",
"@org_tensorflow//tensorflow/core:framework",
"@org_tensorflow//tensorflow/core:lib",
"@org_tensorflow//tensorflow/core:protos_all_cc",
],
# Fuzzer binaries should not be installed or run by default.
tags = ["manual", "fuzzer", "oss-fuzz"],
)
133 changes: 133 additions & 0 deletions tensorflow_serving/util/json_tensor_fuzzer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright 2024 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
//
// https://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.

// OSS-Fuzz harness for the TensorFlow Serving JSON tensor parser.
//
// Targets the three HTTP REST API parsing entry points in json_tensor.cc:
// - FillClassificationRequestFromJson (/v1/models/*:classify)
// - FillRegressionRequestFromJson (/v1/models/*:regress)
// - FillPredictRequestFromJson (/v1/models/*:predict)
//
// These functions process untrusted user-supplied HTTP request bodies and are
// the primary attack surface for the TF Serving REST API. CVE-2025-0649
// (unbounded recursion in GetDenseTensorShape / FillTensorProto) was found in
// this path. This harness ensures continuous regression coverage.

#include <cstddef>
#include <cstdint>
#include <string>

#include "absl/strings/string_view.h"
#include "google/protobuf/map.h"
#include "tensorflow/core/framework/tensor_shape.pb.h"
#include "tensorflow/core/framework/types.pb.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/protobuf/meta_graph.pb.h"
#include "tensorflow_serving/apis/classification.pb.h"
#include "tensorflow_serving/apis/predict.pb.h"
#include "tensorflow_serving/apis/regression.pb.h"
#include "tensorflow_serving/util/json_tensor.h"

namespace {

// Provides a minimal single-input float tensorinfo_map for the predict path.
// Using DT_FLOAT covers the most common numeric tensor type and exercises the
// full GetDenseTensorShape + FillTensorProto recursion path.
tensorflow::Status MockGetTensorInfoMap(
const std::string& /*signature_name*/,
google::protobuf::Map<std::string, tensorflow::TensorInfo>* map) {
tensorflow::TensorInfo info;
info.set_dtype(tensorflow::DT_FLOAT);
// Unbounded shape — allows any input shape from the fuzz corpus.
info.mutable_tensor_shape()->set_unknown_rank(true);
(*map)["input"] = info;
return tensorflow::OkStatus();
}

// Same as above but for DT_STRING inputs (exercises the bytes/b64 path).
tensorflow::Status MockGetStringTensorInfoMap(
const std::string& /*signature_name*/,
google::protobuf::Map<std::string, tensorflow::TensorInfo>* map) {
tensorflow::TensorInfo info;
info.set_dtype(tensorflow::DT_STRING);
info.mutable_tensor_shape()->set_unknown_rank(true);
(*map)["input"] = info;
return tensorflow::OkStatus();
}

// Same as above but for DT_INT64 inputs.
tensorflow::Status MockGetInt64TensorInfoMap(
const std::string& /*signature_name*/,
google::protobuf::Map<std::string, tensorflow::TensorInfo>* map) {
tensorflow::TensorInfo info;
info.set_dtype(tensorflow::DT_INT64);
info.mutable_tensor_shape()->set_unknown_rank(true);
(*map)["input"] = info;
return tensorflow::OkStatus();
}

} // namespace

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
const absl::string_view json(reinterpret_cast<const char*>(data), size);

// --- Classify endpoint ---
// POST /v1/models/{model}:classify
// Exercises MakeExampleFromJsonObject -> AddValueToFeature
{
tensorflow::serving::ClassificationRequest req;
(void)tensorflow::serving::FillClassificationRequestFromJson(json, &req);
}

// --- Regress endpoint ---
// POST /v1/models/{model}:regress
// Same parsing path as classify.
{
tensorflow::serving::RegressionRequest req;
(void)tensorflow::serving::FillRegressionRequestFromJson(json, &req);
}

// --- Predict endpoint: float input ---
// POST /v1/models/{model}:predict
// Exercises GetDenseTensorShape + FillTensorProto (the CVE-2025-0649 path).
{
tensorflow::serving::PredictRequest req;
tensorflow::serving::JsonPredictRequestFormat format =
tensorflow::serving::JsonPredictRequestFormat::kInvalid;
(void)tensorflow::serving::FillPredictRequestFromJson(
json, MockGetTensorInfoMap, &req, &format);
}

// --- Predict endpoint: string/bytes input ---
// Exercises the base64 decode path (JsonDecodeBase64Object).
{
tensorflow::serving::PredictRequest req;
tensorflow::serving::JsonPredictRequestFormat format =
tensorflow::serving::JsonPredictRequestFormat::kInvalid;
(void)tensorflow::serving::FillPredictRequestFromJson(
json, MockGetStringTensorInfoMap, &req, &format);
}

// --- Predict endpoint: int64 input ---
// Exercises integer parsing and type-checking paths.
{
tensorflow::serving::PredictRequest req;
tensorflow::serving::JsonPredictRequestFormat format =
tensorflow::serving::JsonPredictRequestFormat::kInvalid;
(void)tensorflow::serving::FillPredictRequestFromJson(
json, MockGetInt64TensorInfoMap, &req, &format);
}

return 0;
}