ProtobufHandlerFuzzer.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you 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.
*/
package org.apache.calcite.avatica.fuzz;
import org.apache.calcite.avatica.remote.ProtobufTranslationImpl;
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.google.protobuf.ByteString;
import java.io.IOException;
/**
* Fuzzer for ProtobufHandler (ProtobufTranslation).
*/
public class ProtobufHandlerFuzzer {
private ProtobufHandlerFuzzer() {
}
private static final ProtobufTranslationImpl TRANSLATOR = new ProtobufTranslationImpl();
/**
* Fuzzes Protobuf serialization/deserialization for Avatica Request/Response objects.
*
* @param data fuzzed data
*/
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
try {
// The goal here is to hit the protobuf deserialization logic.
// Avatica maps Protobuf messages (WireMessage) into its POJO Service.Request models.
// WireMessage requires a "name" string matching a Request subclass.
boolean isRequest = data.consumeBoolean();
if (isRequest) {
String subType = data.pickValue(new String[]{
"org.apache.calcite.avatica.proto.Requests$CatalogsRequest",
"org.apache.calcite.avatica.proto.Requests$SchemasRequest",
"org.apache.calcite.avatica.proto.Requests$TablesRequest",
"org.apache.calcite.avatica.proto.Requests$TableTypesRequest",
"org.apache.calcite.avatica.proto.Requests$TypeInfoRequest",
"org.apache.calcite.avatica.proto.Requests$ColumnsRequest",
"org.apache.calcite.avatica.proto.Requests$ExecuteRequest",
"org.apache.calcite.avatica.proto.Requests$PrepareRequest",
"org.apache.calcite.avatica.proto.Requests$PrepareAndExecuteRequest",
"org.apache.calcite.avatica.proto.Requests$FetchRequest",
"org.apache.calcite.avatica.proto.Requests$CreateStatementRequest",
"org.apache.calcite.avatica.proto.Requests$CloseStatementRequest",
"org.apache.calcite.avatica.proto.Requests$OpenConnectionRequest",
"org.apache.calcite.avatica.proto.Requests$CloseConnectionRequest",
"org.apache.calcite.avatica.proto.Requests$ConnectionSyncRequest",
"org.apache.calcite.avatica.proto.Requests$DatabasePropertyRequest",
"org.apache.calcite.avatica.proto.Requests$SyncResultsRequest",
"org.apache.calcite.avatica.proto.Requests$CommitRequest",
"org.apache.calcite.avatica.proto.Requests$RollbackRequest",
"org.apache.calcite.avatica.proto.Requests$PrepareAndExecuteBatchRequest",
"org.apache.calcite.avatica.proto.Requests$ExecuteBatchRequest"
});
org.apache.calcite.avatica.proto.Common.WireMessage wireMsg =
org.apache.calcite.avatica.proto.Common.WireMessage.newBuilder()
.setName(subType)
.setWrappedMessage(ByteString.copyFrom(data.consumeRemainingAsBytes()))
.build();
byte[] protobufPayload = wireMsg.toByteArray();
TRANSLATOR.parseRequest(protobufPayload);
} else {
String subType = data.pickValue(new String[]{
"org.apache.calcite.avatica.proto.Responses$OpenConnectionResponse",
"org.apache.calcite.avatica.proto.Responses$CloseConnectionResponse",
"org.apache.calcite.avatica.proto.Responses$CloseStatementResponse",
"org.apache.calcite.avatica.proto.Responses$ConnectionSyncResponse",
"org.apache.calcite.avatica.proto.Responses$CreateStatementResponse",
"org.apache.calcite.avatica.proto.Responses$DatabasePropertyResponse",
"org.apache.calcite.avatica.proto.Responses$ExecuteResponse",
"org.apache.calcite.avatica.proto.Responses$FetchResponse",
"org.apache.calcite.avatica.proto.Responses$PrepareResponse",
"org.apache.calcite.avatica.proto.Responses$ResultSetResponse",
"org.apache.calcite.avatica.proto.Responses$ErrorResponse",
"org.apache.calcite.avatica.proto.Responses$SyncResultsResponse",
"org.apache.calcite.avatica.proto.Responses$RpcMetadata",
"org.apache.calcite.avatica.proto.Responses$CommitResponse",
"org.apache.calcite.avatica.proto.Responses$RollbackResponse",
"org.apache.calcite.avatica.proto.Responses$ExecuteBatchResponse"
});
org.apache.calcite.avatica.proto.Common.WireMessage wireMsg =
org.apache.calcite.avatica.proto.Common.WireMessage.newBuilder()
.setName(subType)
.setWrappedMessage(ByteString.copyFrom(data.consumeRemainingAsBytes()))
.build();
byte[] protobufPayload = wireMsg.toByteArray();
TRANSLATOR.parseResponse(protobufPayload);
}
} catch (IOException e) {
// Known exception from protobuf parsing (e.g. InvalidProtocolBufferException)
} catch (IllegalArgumentException | IllegalStateException | NullPointerException e) {
// Known issues when Protobuf unmarshalls into Avatica types that fail preconditions
} catch (RuntimeException e) {
// Specifically catching Avatica's custom DeserializationException or
// unhandled protobuf issues to ensure the fuzzer survives
if (e.getClass().getName().contains("DeserializationException")
|| e.getClass().getName().contains("InvalidProtocolBufferException")
|| e.getMessage() != null && e.getMessage().contains("Unknown type:")
|| e.getMessage() != null && e.getMessage().contains("Unhandled type:")) {
return;
}
// If it's a real bug (NullPointerException, etc), let it crash!
throw e;
}
}
}