summaryrefslogtreecommitdiffstats
path: root/tests/fuzz
diff options
context:
space:
mode:
Diffstat (limited to 'tests/fuzz')
-rw-r--r--tests/fuzz/FuzzerInterface.h81
-rw-r--r--tests/fuzz/LUKS2.proto379
-rw-r--r--tests/fuzz/LUKS2_plain_JSON.proto190
-rw-r--r--tests/fuzz/Makefile.am122
-rw-r--r--tests/fuzz/README.md66
-rw-r--r--tests/fuzz/crypt2_load_fuzz.cc112
-rw-r--r--tests/fuzz/crypt2_load_fuzz.dict130
-rw-r--r--tests/fuzz/crypt2_load_ondisk_fuzz.cc64
-rw-r--r--tests/fuzz/crypt2_load_ondisk_fuzz.dict9
-rw-r--r--tests/fuzz/crypt2_load_proto_fuzz.cc51
-rw-r--r--tests/fuzz/crypt2_load_proto_plain_json_fuzz.cc51
-rw-r--r--tests/fuzz/crypt2_load_proto_plain_json_fuzz.dict72
-rw-r--r--tests/fuzz/json_proto_converter.cc87
-rw-r--r--tests/fuzz/json_proto_converter.h43
-rwxr-xr-xtests/fuzz/oss-fuzz-build.sh152
-rw-r--r--tests/fuzz/plain_json_proto_to_luks2.cc75
-rw-r--r--tests/fuzz/plain_json_proto_to_luks2_converter.cc153
-rw-r--r--tests/fuzz/plain_json_proto_to_luks2_converter.h58
-rw-r--r--tests/fuzz/proto_to_luks2.cc75
-rw-r--r--tests/fuzz/proto_to_luks2_converter.cc604
-rw-r--r--tests/fuzz/proto_to_luks2_converter.h91
-rw-r--r--tests/fuzz/unpoison-mutated-buffers-from-libfuzzer.patch29
22 files changed, 2694 insertions, 0 deletions
diff --git a/tests/fuzz/FuzzerInterface.h b/tests/fuzz/FuzzerInterface.h
new file mode 100644
index 0000000..b238253
--- /dev/null
+++ b/tests/fuzz/FuzzerInterface.h
@@ -0,0 +1,81 @@
+// Based on https://github.com/llvm-mirror/compiler-rt/blob/master/lib/fuzzer/FuzzerInterface.h
+//
+//===- FuzzerInterface.h - Interface header for the Fuzzer ------*- C++ -* ===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+// Define the interface between libFuzzer and the library being tested.
+//===----------------------------------------------------------------------===//
+
+// NOTE: the libFuzzer interface is thin and in the majority of cases
+// you should not include this file into your target. In 95% of cases
+// all you need is to define the following function in your file:
+// extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size);
+
+// WARNING: keep the interface in C.
+
+#ifndef LLVM_FUZZER_INTERFACE_H
+#define LLVM_FUZZER_INTERFACE_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+// Define FUZZER_INTERFACE_VISIBILITY to set default visibility in a way that
+// doesn't break MSVC.
+#if defined(_WIN32)
+#define FUZZER_INTERFACE_VISIBILITY __declspec(dllexport)
+#else
+#define FUZZER_INTERFACE_VISIBILITY __attribute__((visibility("default")))
+#endif
+
+// Mandatory user-provided target function.
+// Executes the code under test with [Data, Data+Size) as the input.
+// libFuzzer will invoke this function *many* times with different inputs.
+// Must return 0.
+FUZZER_INTERFACE_VISIBILITY int
+LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size);
+
+// Optional user-provided initialization function.
+// If provided, this function will be called by libFuzzer once at startup.
+// It may read and modify argc/argv.
+// Must return 0.
+FUZZER_INTERFACE_VISIBILITY int LLVMFuzzerInitialize(int *argc, char ***argv);
+
+// Optional user-provided custom mutator.
+// Mutates raw data in [Data, Data+Size) inplace.
+// Returns the new size, which is not greater than MaxSize.
+// Given the same Seed produces the same mutation.
+FUZZER_INTERFACE_VISIBILITY size_t
+LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize,
+ unsigned int Seed);
+
+// Optional user-provided custom cross-over function.
+// Combines pieces of Data1 & Data2 together into Out.
+// Returns the new size, which is not greater than MaxOutSize.
+// Should produce the same mutation given the same Seed.
+FUZZER_INTERFACE_VISIBILITY size_t
+LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1,
+ const uint8_t *Data2, size_t Size2, uint8_t *Out,
+ size_t MaxOutSize, unsigned int Seed);
+
+// Experimental, may go away in future.
+// libFuzzer-provided function to be used inside LLVMFuzzerCustomMutator.
+// Mutates raw data in [Data, Data+Size) inplace.
+// Returns the new size, which is not greater than MaxSize.
+FUZZER_INTERFACE_VISIBILITY size_t
+LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize);
+
+#undef FUZZER_INTERFACE_VISIBILITY
+
+#ifdef __cplusplus
+} // extern "C"
+#endif // __cplusplus
+
+#endif // LLVM_FUZZER_INTERFACE_H
diff --git a/tests/fuzz/LUKS2.proto b/tests/fuzz/LUKS2.proto
new file mode 100644
index 0000000..3a0f287
--- /dev/null
+++ b/tests/fuzz/LUKS2.proto
@@ -0,0 +1,379 @@
+/*
+ * cryptsetup LUKS2 custom mutator
+ *
+ * Copyright (C) 2022-2023 Daniel Zatovic <daniel.zatovic@gmail.com>
+ * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+syntax = "proto2";
+
+package LUKS2_proto;
+
+// ---------------------------------------------------------------------------
+// ----------------------------- GENERIC OBJECTS -----------------------------
+// ---------------------------------------------------------------------------
+
+message object_id {
+ oneof id {
+ // int_id will be mapped to range -16 to 16 (mod 33)
+ // this way iy should be easier to generate valid
+ // object cross-references
+ uint32 int_id = 1;
+ string string_id = 2;
+ }
+}
+
+message string_uint64 {
+ required bool negative = 1;
+ oneof number {
+ uint32 uint_num = 2;
+ string string_num = 3;
+ }
+}
+
+enum hash_algorithm {
+ HASH_ALG_SHA1 = 1;
+ HASH_ALG_SHA256 = 2;
+}
+
+
+// ---------------------------------------------------------------------------
+// ----------------------------- BINARY HEADER -------------------------------
+// ---------------------------------------------------------------------------
+
+enum luks2_magic {
+ INVALID = 0;
+ FIRST = 1;
+ SECOND = 2;
+}
+
+enum luks_version {
+ ONE = 1;
+ TWO = 2;
+ THREE = 3;
+}
+
+// we limit the size to 64KiB to make the fuzzing faster
+// because the checksum needs to be calculated for the whole image
+enum hdr_size {
+ size_16_KB = 16384;
+ size_32_KB = 32768;
+ size_64_KB = 65536;
+// size_128_KB = 131072;
+// size_256_KB = 262144;
+// size_512_KB = 524288;
+// size_1_MB = 1048576;
+// size_2_MB = 2097152;
+// size_4_MB = 4194304;
+}
+
+enum seqid_description {
+ PRIMARY_GREATER = 0;
+ SECONDARY_GREATER = 1;
+ EQUAL = 2;
+}
+
+// message luks2_hdr_disk {
+// char magic[LUKS2_MAGIC_L];
+// //uint16_t version; /* Version 2 */
+// uint64_t hdr_size; /* in bytes, including JSON area */
+// uint64_t seqid; /* increased on every update */
+// char label[LUKS2_LABEL_L];
+// char checksum_alg[LUKS2_CHECKSUM_ALG_L];
+// uint8_t salt[LUKS2_SALT_L]; /* unique for every header/offset */
+// char uuid[LUKS2_UUID_L];
+// char subsystem[LUKS2_LABEL_L]; /* owner subsystem label */
+// uint64_t hdr_offset; /* offset from device start in bytes */
+// char _padding[184];
+// uint8_t csum[LUKS2_CHECKSUM_L];
+// }
+message LUKS2_header {
+ required luks_version version = 1;
+ required luks2_magic magic = 2;
+ required hdr_size hdr_size = 3;
+ required bool use_correct_checksum = 4;
+
+ optional uint64 selected_offset = 5;
+}
+
+message LUKS2_both_headers {
+ required LUKS2_header primary_header = 1;
+ required LUKS2_header secondary_header = 2;
+
+ required seqid_description seqid = 3;
+ required json_area_description json_area = 4;
+}
+
+message json_area_description {
+ optional config_description config = 1;
+ repeated keyslot_description keyslots = 2;
+ repeated digest_description digests = 3;
+ repeated segment_description segments = 4;
+ repeated token_description tokens = 5;
+}
+
+// ---------------------------------------------------------------------------
+// ----------------------------- KEYSLOT OBJECT ------------------------------
+// ---------------------------------------------------------------------------
+
+enum keyslot_type {
+ KEYSLOT_TYPE_LUKS2 = 1;
+ KEYSLOT_TYPE_REENCRYPT = 2;
+ KEYSLOT_TYPE_PLACEHOLDER = 3;
+}
+
+enum reencrypt_keyslot_mode {
+ MODE_REENCRYPT = 1;
+ MODE_ENCRYPT = 2;
+ MODE_DECRYPT = 3;
+}
+
+enum reencrypt_keyslot_direction {
+ DIRECTION_FORWARD = 1;
+ DIRECTION_BACKWARD = 2;
+}
+
+// The area object contains these mandatory fields:
+// - type [string] the area type.
+// - offset [string-uint64] the offset from the device start to the beginning of the binary area (in bytes).
+// - size [string-uint64] the area size (in bytes).
+//
+// Area type raw contains these additional fields:
+// - encryption [string] the area encryption algorithm, in dm-crypt notation (for example aes-xts-plain64).
+// - key_size [integer] the area encryption key size.
+//
+// Area type none and journal (used only for reencryption optional extension) contain only mandatory fields.
+//
+// Area type checksum (used only for reencryption optional extension) contains these additional fields:
+// - hash [string] The hash algorithm for the checksum resilience mode.
+// - sector_size [integer] The data unit size for digest checksum calculated with the hash algorithm.
+//
+// Area type datashift (used only for reencryption optional extension) contains this additional field:
+// - shift_size [string-uint64] The data shift (in bytes) performed during reencryption (shift direction is according to direction field).
+
+enum keyslot_area_type {
+ KEYSLOT_AREA_TYPE_RAW = 1;
+ KEYSLOT_AREA_TYPE_NONE = 2;
+ KEYSLOT_AREA_TYPE_JOURNAL = 3;
+ KEYSLOT_AREA_TYPE_CHECKSUM = 4;
+ KEYSLOT_AREA_TYPE_DATASHIFT = 5;
+}
+
+message keyslot_area_description {
+ // mandatory fields
+ optional keyslot_area_type type = 1;
+ optional string_uint64 offset = 2;
+ optional string_uint64 size = 3;
+
+ // raw type fields
+ optional string encryption = 4;
+ optional int32 key_size = 5;
+
+ // checksum type field
+ optional hash_algorithm hash = 6;
+ optional int32 sector_size = 7;
+
+ // datashift type fields
+ optional string_uint64 shift_size = 8;
+}
+
+// The object describes PBKDF attributes used for the keyslot.
+// The kdf object mandatory fields are:
+// - type [string] the PBKDF type.
+// - salt [base64] the salt for PBKDF (binary data).
+//
+// The pbkdf2 type (compatible with LUKS1) contains these additional fields:
+// - hash [string] the hash algorithm for the PBKDF2 (SHA-256).
+// - iterations [integer] the PBKDF2 iterations count.
+//
+// The argon2i and argon2id type contains these additional fields:
+// - time [integer] the time cost (in fact the iterations count for Argon2).
+// - memory [integer] the memory cost, in kilobytes. If not available, the keyslot cannot be unlocked.
+// - cpus [integer] the required number of threads (CPU cores number cost). If not available, unlocking will be slower.
+
+enum keyslot_kdf_type {
+ KEYSLOT_KDF_TYPE_PBKDF2 = 1;
+ KEYSLOT_KDF_TYPE_ARGON2I = 2;
+ KEYSLOT_KDF_TYPE_ARGON2ID = 3;
+}
+
+message keyslot_kdf_description {
+ optional keyslot_kdf_type type = 1;
+ optional string salt = 2;
+
+ // pbkdf2 type
+ optional hash_algorithm hash = 3;
+ optional int32 iterations = 4;
+
+ // argon2i and argon2id types
+ optional int32 time = 5;
+ optional int32 memory = 6;
+ optional int32 cpus = 7;
+}
+
+enum keyslot_af_type {
+ KEYSLOT_AF_TYPE_LUKS1 = 1;
+}
+
+// The af (anti-forensic splitter) object contains this madatory field:
+// - type [string] the anti-forensic function type.
+// AF type luks1 (compatible with LUKS1 [1]) contains these additional fields:
+// - stripes [integer] the number of stripes, for historical reasons only the 4000 value is supported.
+// - hash [string] the hash algorithm used.
+
+message keyslot_af_description {
+ optional keyslot_af_type type = 1;
+ optional int32 stripes = 2;
+ optional hash_algorithm hash = 3;
+}
+
+// - type [string] the keyslot type.
+// - key_size [integer] the key size (in bytes) stored in keyslot.
+// - priority [integer,optional] the keyslot priority. Here 0 means ignore (the slot should be used only if explicitly stated), 1 means normal priority and 2 means high priority (tried before normal priority).
+
+// REENCRYPT
+// The key size field must be set to 1. The area type must be none, checksum,
+// journal or datashift.
+// The reencrypt object must contain these additional fields:
+// - mode [string] the reencryption mode. reencrypt, encrypt and decrypt
+// - direction [string] the reencryption direction. forward backward
+
+// - area [object] the allocated area in the binary keyslots area.
+// LUKS2 object must contain these additional fields:
+// - kdf [object] the PBKDF type and parameters used.
+// - af [object] the anti-forensic splitter [1] (only the luks1 type is currently
+// used).
+
+message keyslot_description {
+ // type
+ required object_id oid = 1;
+
+ optional keyslot_type type = 2;
+ optional int32 key_size = 3;
+ optional int32 priority = 4;
+
+ // reencrypt extension
+ optional reencrypt_keyslot_mode mode = 5;
+ optional reencrypt_keyslot_direction direction = 6;
+
+ // objects
+ optional keyslot_area_description area = 7;
+ optional keyslot_kdf_description kdf = 8;
+ optional keyslot_af_description af = 9;
+}
+
+// ---------------------------------------------------------------------------
+// ------------------------------ DIGEST OBJECT ------------------------------
+// ---------------------------------------------------------------------------
+
+message digest_description {
+ required object_id oid = 1;
+
+ optional keyslot_kdf_type type = 2;
+ repeated object_id keyslots = 3;
+ repeated object_id segments = 4;
+ optional string salt = 5;
+ optional string digest = 6;
+
+ // pbkdf2 digest fields
+ optional hash_algorithm hash = 7;
+ optional int32 iterations = 8;
+}
+
+// ---------------------------------------------------------------------------
+// ----------------------------- SEGMENT OBJECT ------------------------------
+// ---------------------------------------------------------------------------
+
+enum segment_type {
+ SEGMENT_TYPE_LINEAR = 1;
+ SEGMENT_TYPE_CRYPT = 2;
+}
+
+enum segment_flag {
+ IN_REENCRYPTION = 1;
+ BACKUP_FINAL = 2;
+ BACKUP_PREVIOUS = 3;
+ BACKUP_MOVED_SEGMENT = 4;
+}
+
+message segment_integrity_description {
+ optional string type = 1;
+ optional string journal_encryption = 2;
+ optional string journal_integrity = 3;
+}
+
+message segment_description {
+ required object_id oid = 1;
+ optional segment_type type = 2;
+ optional string_uint64 offset = 3;
+ optional string_uint64 size = 4;
+ repeated segment_flag flags = 5;
+
+ // segment type crypt
+ optional string_uint64 iv_tweak = 6;
+ optional string encryption = 7;
+ optional int32 sector_size = 8;
+ optional segment_integrity_description integrity = 9;
+}
+
+// ---------------------------------------------------------------------------
+// ------------------------------ TOKEN OBJECT -------------------------------
+// ---------------------------------------------------------------------------
+
+message token_description {
+ required object_id oid = 1;
+
+ optional string type = 2;
+ repeated object_id keyslots = 3;
+ optional string key_description = 4;
+}
+
+// ---------------------------------------------------------------------------
+// ------------------------------ CONFIG OBJECT ------------------------------
+// ---------------------------------------------------------------------------
+
+// - allow-discards allows TRIM (discards) on the active device.
+// - same-cpu-crypt compatibility performance flag for dm-crypt [3] to per- form encryption using the same CPU that originated the request.
+// - submit-from-crypt-cpus compatibility performance flag for dm-crypt [3] to disable offloading write requests to a separate thread after encryption.
+// - no-journal disable data journalling for dm-integrity [10].
+// - no-read-workqueue compatibility performance flag for dm-crypt [3] to bypass dm-crypt read workqueue and process read requests synchronously.
+// - no-write-workqueue compatibility performance flag for dm-crypt [3] to bypass dm-crypt write workqueue and process write requests synchronously.
+enum config_flag {
+ CONFIG_FLAG_ALLOW_DISCARDS = 1;
+ CONFIG_FLAG_SAME_CPU_CRYPT = 2;
+ CONFIG_FLAG_SUBMIT_FROM_CRYPT_CPUS = 3;
+ CONFIG_FLAG_NO_JOURNAL = 4;
+ CONFIG_FLAG_NO_READ_WORKQUEUE = 5;
+ CONFIG_FLAG_NO_WRITE_WORKQUEUE = 6;
+}
+
+enum config_requirement {
+ CONFIG_REQUIREMENT_OFFLINE_REENCRYPT = 1;
+ CONFIG_REQUIREMENT_ONLINE_REENCRYPT_V2 = 2;
+}
+
+// - json_size [string-uint64] the JSON area size (in bytes). Must match the binary header.
+// - keyslots_size [string-uint64] the binary keyslot area size (in bytes). Must be aligned to 4096 bytes.
+// - flags [array, optional] the array of string objects with persistent flags for the device.
+// - requirements [array, optional] the array of string objects with additional required features for the LUKS device.
+
+message config_description {
+ required bool use_primary_hdr_size = 2;
+
+ repeated config_flag config_flags = 3;
+ repeated config_requirement requirements = 4;
+}
diff --git a/tests/fuzz/LUKS2_plain_JSON.proto b/tests/fuzz/LUKS2_plain_JSON.proto
new file mode 100644
index 0000000..59096b7
--- /dev/null
+++ b/tests/fuzz/LUKS2_plain_JSON.proto
@@ -0,0 +1,190 @@
+/*
+ * cryptsetup LUKS2 custom mutator
+ *
+ * Copyright (C) 2022-2023 Daniel Zatovic <daniel.zatovic@gmail.com>
+ * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+syntax = "proto2";
+
+package json_proto;
+
+// ---------------------------------------------------------------------------
+// ----------------------------- GENERIC OBJECTS -----------------------------
+// ---------------------------------------------------------------------------
+
+message object_id {
+ oneof id {
+ // int_id will be mapped to range -16 to 16 (mod 33)
+ // this way iy should be easier to generate valid
+ // object cross-references
+ uint32 int_id = 1;
+ string string_id = 2;
+ }
+}
+
+message string_uint64 {
+ required bool negative = 1;
+ oneof number {
+ uint32 uint_num = 2;
+ string string_num = 3;
+ }
+}
+
+enum hash_algorithm {
+ HASH_ALG_SHA1 = 1;
+ HASH_ALG_SHA256 = 2;
+}
+
+
+// ---------------------------------------------------------------------------
+// ----------------------------- BINARY HEADER -------------------------------
+// ---------------------------------------------------------------------------
+
+enum luks2_magic {
+ INVALID = 0;
+ FIRST = 1;
+ SECOND = 2;
+}
+
+enum luks_version {
+ ONE = 1;
+ TWO = 2;
+ THREE = 3;
+}
+
+// we limit the size to 64KiB to make the fuzzing faster
+// because the checksum needs to be calculated for the whole image
+enum hdr_size {
+ size_16_KB = 16384;
+ size_32_KB = 32768;
+ size_64_KB = 65536;
+// size_128_KB = 131072;
+// size_256_KB = 262144;
+// size_512_KB = 524288;
+// size_1_MB = 1048576;
+// size_2_MB = 2097152;
+// size_4_MB = 4194304;
+}
+
+enum seqid_description {
+ PRIMARY_GREATER = 0;
+ SECONDARY_GREATER = 1;
+ EQUAL = 2;
+}
+
+// message luks2_hdr_disk {
+// char magic[LUKS2_MAGIC_L];
+// //uint16_t version; /* Version 2 */
+// uint64_t hdr_size; /* in bytes, including JSON area */
+// uint64_t seqid; /* increased on every update */
+// char label[LUKS2_LABEL_L];
+// char checksum_alg[LUKS2_CHECKSUM_ALG_L];
+// uint8_t salt[LUKS2_SALT_L]; /* unique for every header/offset */
+// char uuid[LUKS2_UUID_L];
+// char subsystem[LUKS2_LABEL_L]; /* owner subsystem label */
+// uint64_t hdr_offset; /* offset from device start in bytes */
+// char _padding[184];
+// uint8_t csum[LUKS2_CHECKSUM_L];
+// }
+message LUKS2_header {
+ required luks_version version = 1;
+ required luks2_magic magic = 2;
+ required hdr_size hdr_size = 3;
+ required bool use_correct_checksum = 4;
+
+ optional uint64 selected_offset = 5;
+}
+
+message LUKS2_both_headers {
+ required LUKS2_header primary_header = 1;
+ required LUKS2_header secondary_header = 2;
+
+ required seqid_description seqid = 3;
+ required JsonObject json_area = 4;
+}
+
+message JsonObject {
+ required string name = 1;
+ required JsonValue value = 2;
+}
+
+message JsonValue {
+ oneof value {
+ // Json value types:
+
+ // null: null, will be used when 'oneof' contains nothing
+
+ // object: another json object of any type
+ JsonObject object_value = 1;
+
+ // array: an array of values
+ ArrayValue array_value = 2;
+
+ // number: can be an integer, a float, an exponent
+ NumberValue number_value = 3;
+
+ // string: unicode string
+ StringValue string_value = 4;
+
+ // boolean: true or talse
+ BooleanValue boolean_value = 5;
+ }
+}
+
+message ArrayValue {
+ repeated JsonValue value = 1;
+}
+
+message NumberInteger {
+ required int64 value = 1;
+}
+
+message NumberFloat {
+ required double value = 1;
+}
+
+message NumberExponent {
+ required int32 base = 1;
+ required int32 exponent = 2;
+ required bool use_uppercase = 3;
+}
+
+message NumberExponentFrac {
+ required float base = 1;
+ required int32 exponent = 2;
+ required bool use_uppercase = 3;
+}
+
+message NumberValue {
+ required NumberInteger integer_value = 1;
+
+ // integer_value is used when oneof field below has nothing.
+ oneof value {
+ NumberFloat float_value = 2;
+ NumberExponent exponent_value = 3;
+ NumberExponentFrac exponent_frac_value = 4;
+ }
+}
+
+message StringValue {
+ required string value = 1;
+}
+
+message BooleanValue {
+ required bool value = 1;
+}
diff --git a/tests/fuzz/Makefile.am b/tests/fuzz/Makefile.am
new file mode 100644
index 0000000..c7a6cdf
--- /dev/null
+++ b/tests/fuzz/Makefile.am
@@ -0,0 +1,122 @@
+EXTRA_DIST = README.md oss-fuzz-build.sh
+dist_noinst_DATA = \
+ LUKS2.proto \
+ LUKS2_plain_JSON.proto \
+ crypt2_load_fuzz.dict \
+ crypt2_load_ondisk_fuzz.dict \
+ crypt2_load_proto_plain_json_fuzz.dict \
+ unpoison-mutated-buffers-from-libfuzzer.patch
+CLEANFILES = \
+ LUKS2.pb.h \
+ LUKS2.pb.cc \
+ LUKS2_plain_JSON.pb.h \
+ LUKS2_plain_JSON.pb.cc
+
+distclean-local:
+ -rm -rf out build
+
+LIB_FUZZING_ENGINE := $(if $(LIB_FUZZING_ENGINE),$(LIB_FUZZING_ENGINE),"-fsanitize=fuzzer")
+SANITIZER := $(if $(SANITIZER),,"-fsanitize=address")
+
+DEPS_PATH := $(top_srcdir)/tests/fuzz/build/static_lib_deps
+
+crypt2_load_fuzz_SOURCES = FuzzerInterface.h crypt2_load_fuzz.cc
+crypt2_load_fuzz_LDADD = ../../libcryptsetup.la ../../libcrypto_backend.la -L$(DEPS_PATH)/lib
+crypt2_load_fuzz_LDFLAGS = $(AM_LDFLAGS) $(LIB_FUZZING_ENGINE) $(SANITIZER)
+crypt2_load_fuzz_CXXFLAGS = $(AM_CXXFLAGS) -I$(top_srcdir)/lib -I$(top_srcdir)/tests/fuzz
+
+crypt2_load_ondisk_fuzz_SOURCES = FuzzerInterface.h crypt2_load_ondisk_fuzz.cc
+crypt2_load_ondisk_fuzz_LDADD = ../../libcryptsetup.la -L$(DEPS_PATH)/lib
+crypt2_load_ondisk_fuzz_LDFLAGS = $(AM_LDFLAGS) $(LIB_FUZZING_ENGINE) $(SANITIZER)
+crypt2_load_ondisk_fuzz_CXXFLAGS = $(AM_CXXFLAGS) -I$(top_srcdir)/lib -I$(top_srcdir)/tests/fuzz
+
+test-environment-m:
+ @ if test ! -d $(DEPS_PATH); then \
+ echo "You need to build static libraries first; use oss-fuzz-build.sh script."; \
+ exit 1; \
+ fi
+test-environment: | test-environment-m $(DEPS_PATH)
+
+LUKS2.pb.h: LUKS2.proto
+ $(DEPS_PATH)/bin/protoc LUKS2.proto --cpp_out=.
+LUKS2.pb.cc: LUKS2.pb.h
+
+LUKS2_plain_JSON.pb.h: LUKS2_plain_JSON.proto
+ $(DEPS_PATH)/bin/protoc LUKS2_plain_JSON.proto --cpp_out=.
+LUKS2_plain_JSON.pb.cc: LUKS2_plain_JSON.pb.h
+
+crypt2_load_proto_fuzz-crypt2_load_proto_fuzz.$(OBJEXT): LUKS2.pb.cc
+crypt2_load_proto_plain_json_fuzz-crypt2_load_proto_plain_json_fuzz.$(OBJEXT): LUKS2_plain_JSON.pb.cc
+
+nodist_crypt2_load_proto_fuzz_SOURCES = LUKS2.pb.h LUKS2.pb.cc
+crypt2_load_proto_fuzz_SOURCES = FuzzerInterface.h \
+ crypt2_load_proto_fuzz.cc \
+ proto_to_luks2_converter.h \
+ proto_to_luks2_converter.cc
+crypt2_load_proto_fuzz_LDADD = \
+ ../../libcryptsetup.la \
+ ../../libcrypto_backend.la \
+ -L$(DEPS_PATH)/lib -lprotobuf-mutator-libfuzzer -lprotobuf-mutator -lprotobuf
+crypt2_load_proto_fuzz_LDFLAGS = $(AM_LDFLAGS) $(LIB_FUZZING_ENGINE) $(SANITIZER)
+crypt2_load_proto_fuzz_CXXFLAGS = $(AM_CXXFLAGS) \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/tests/fuzz \
+ -I$(DEPS_PATH)/include \
+ -I$(DEPS_PATH)/include/libprotobuf-mutator -I$(DEPS_PATH)/include/libprotobuf-mutator/src
+
+nodist_crypt2_load_proto_plain_json_fuzz_SOURCES = LUKS2_plain_JSON.pb.h LUKS2_plain_JSON.pb.cc
+crypt2_load_proto_plain_json_fuzz_SOURCES = FuzzerInterface.h \
+ crypt2_load_proto_plain_json_fuzz.cc \
+ json_proto_converter.h \
+ json_proto_converter.cc \
+ plain_json_proto_to_luks2_converter.h \
+ plain_json_proto_to_luks2_converter.cc
+crypt2_load_proto_plain_json_fuzz_LDADD = \
+ ../../libcryptsetup.la \
+ ../../libcrypto_backend.la \
+ -L$(DEPS_PATH)/lib -lprotobuf-mutator-libfuzzer -lprotobuf-mutator -lprotobuf
+crypt2_load_proto_plain_json_fuzz_LDFLAGS = $(AM_LDFLAGS) $(LIB_FUZZING_ENGINE) $(SANITIZER)
+crypt2_load_proto_plain_json_fuzz_CXXFLAGS = $(AM_CXXFLAGS) \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/tests/fuzz \
+ -I$(DEPS_PATH)/include \
+ -I$(DEPS_PATH)/include/libprotobuf-mutator -I$(DEPS_PATH)/include/libprotobuf-mutator/src
+
+nodist_proto_to_luks2_SOURCES = LUKS2.pb.h LUKS2.pb.cc
+proto_to_luks2_SOURCES = \
+ proto_to_luks2.cc \
+ proto_to_luks2_converter.h \
+ proto_to_luks2_converter.cc
+proto_to_luks2_LDADD = ../../libcryptsetup.la ../../libcrypto_backend.la -L$(DEPS_PATH)/lib -lprotobuf
+proto_to_luks2_LDFLAGS = $(AM_LDFLAGS) -fsanitize=fuzzer-no-link $(SANITIZER)
+proto_to_luks2_CXXFLAGS = $(AM_CXXFLAGS) \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/tests/fuzz \
+ -I$(DEPS_PATH)/include
+
+nodist_plain_json_proto_to_luks2_SOURCES = LUKS2_plain_JSON.pb.h LUKS2_plain_JSON.pb.cc
+plain_json_proto_to_luks2_SOURCES = \
+ plain_json_proto_to_luks2.cc \
+ plain_json_proto_to_luks2_converter.h \
+ plain_json_proto_to_luks2_converter.cc \
+ json_proto_converter.h \
+ json_proto_converter.cc
+plain_json_proto_to_luks2_LDADD = ../../libcryptsetup.la ../../libcrypto_backend.la -L$(DEPS_PATH)/lib -lprotobuf
+plain_json_proto_to_luks2_LDFLAGS = $(AM_LDFLAGS) -fsanitize=fuzzer-no-link $(SANITIZER)
+plain_json_proto_to_luks2_CXXFLAGS = $(AM_CXXFLAGS) \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/tests/fuzz \
+ -I$(DEPS_PATH)/include
+
+if ENABLE_FUZZ_TARGETS
+noinst_PROGRAMS = \
+ crypt2_load_fuzz \
+ crypt2_load_ondisk_fuzz \
+ crypt2_load_proto_fuzz \
+ crypt2_load_proto_plain_json_fuzz \
+ proto_to_luks2 \
+ plain_json_proto_to_luks2
+
+fuzz-targets: test-environment $(noinst_PROGRAMS)
+.PHONY: fuzz-targets
+endif
diff --git a/tests/fuzz/README.md b/tests/fuzz/README.md
new file mode 100644
index 0000000..fdcfa27
--- /dev/null
+++ b/tests/fuzz/README.md
@@ -0,0 +1,66 @@
+# Fuzzing target for cryptsetup project
+
+This directory contains experimental targets for fuzzing testing.
+It can be run in the OSS-Fuzz project but also compiled separately.
+
+# Requirements
+
+Fuzzers use address sanitizer. To properly detect problems, all
+important libraries must be compiled statically with sanitizer enabled.
+
+Compilation requires *clang* and *clang++* compilers (gcc is not
+supported yet).
+
+# Standalone build
+
+The script `oss-fuzz-build.sh` can be used to prepare the tree
+with pre-compiled library dependencies.
+We use upstream git for projects, which can clash with locally
+installed versions. The best is to use only basic system installation
+without development packages (script will use custom include, libs,
+and pkg-config paths).
+
+# Build Docker image and fuzzers
+
+You can also run OSS-Fuzz in a Docker image, use these commands
+to prepare fuzzers:
+```
+sudo python3 infra/helper.py build_image cryptsetup
+sudo python3 infra/helper.py build_fuzzers cryptsetup
+```
+On SELinux systems also add (https://github.com/google/oss-fuzz/issues/30):
+```
+sudo chcon -Rt svirt_sandbox_file_t build/
+```
+
+# Run LUKS2 fuzzer
+`FUZZER_NAME` can be one of: `crypt2_load_fuzz`, `crypt2_load_proto_fuzz`, `crypt2_load_proto_plain_json_fuzz`
+```
+FUZZER_NAME="crypt2_load_proto_plain_json_fuzz"
+sudo mkdir -p build/corpus/cryptsetup/$FUZZER_NAME
+sudo python infra/helper.py run_fuzzer --corpus-dir build/corpus/cryptsetup/$FUZZER_NAME/ --sanitizer address cryptsetup $FUZZER_NAME '-jobs=8 -workers=8'
+```
+
+The output of the parallel threads will be written to `fuzz-<N>.log` (where `<N>` is the number of the process).
+You can watch it using e.g.:
+```
+tail -f build/out/cryptsetup/fuzz-*
+```
+
+Optionally, you can use experimental `fork` mode for parallelization and the output will be displayed directly on the terminal:
+```
+sudo python infra/helper.py run_fuzzer --corpus-dir build/corpus/cryptsetup/$FUZZER_NAME/ --sanitizer address cryptsetup $FUZZER_NAME '-fork=8 '
+```
+
+# Rebuild fuzz targets for coverage
+```
+sudo python infra/helper.py build_fuzzers --sanitizer coverage cryptsetup
+```
+
+# Generate coverage report
+```
+sudo python infra/helper.py coverage cryptsetup --no-corpus-download --fuzz-target $FUZZER_NAME
+```
+
+# Further information
+For more details, you can look into the [Using fuzzing for Linux disk encryption tools](https://is.muni.cz/th/bum03/?lang=en) thesis.
diff --git a/tests/fuzz/crypt2_load_fuzz.cc b/tests/fuzz/crypt2_load_fuzz.cc
new file mode 100644
index 0000000..1251d72
--- /dev/null
+++ b/tests/fuzz/crypt2_load_fuzz.cc
@@ -0,0 +1,112 @@
+/*
+ * cryptsetup LUKS2 fuzz target
+ *
+ * Copyright (C) 2022-2023 Daniel Zatovic <daniel.zatovic@gmail.com>
+ * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+extern "C" {
+#define FILESIZE (16777216)
+#include "src/cryptsetup.h"
+#include <err.h>
+#include "luks2/luks2.h"
+#include "crypto_backend/crypto_backend.h"
+#include "FuzzerInterface.h"
+
+static int calculate_checksum(const uint8_t* data, size_t size) {
+ struct crypt_hash *hd = NULL;
+ struct luks2_hdr_disk *hdr = NULL;
+ int hash_size;
+ uint64_t hdr_size1, hdr_size2;
+ int r = 0;
+
+ /* primary header */
+ if (sizeof(struct luks2_hdr_disk) > size)
+ return 0;
+ hdr = CONST_CAST(struct luks2_hdr_disk *) data;
+
+ hdr_size1 = be64_to_cpu(hdr->hdr_size);
+ if (hdr_size1 > size)
+ return 0;
+ memset(&hdr->csum, 0, LUKS2_CHECKSUM_L);
+ if ((r = crypt_hash_init(&hd, "sha256")))
+ goto out;
+ if ((r = crypt_hash_write(hd, CONST_CAST(char*) data, hdr_size1)))
+ goto out;
+ hash_size = crypt_hash_size("sha256");
+ if (hash_size <= 0) {
+ r = 1;
+ goto out;
+ }
+ if ((r = crypt_hash_final(hd, (char*)&hdr->csum, (size_t)hash_size)))
+ goto out;
+ crypt_hash_destroy(hd);
+
+ /* secondary header */
+ if (hdr_size1 < sizeof(struct luks2_hdr_disk))
+ hdr_size1 = sizeof(struct luks2_hdr_disk);
+
+ if (hdr_size1 + sizeof(struct luks2_hdr_disk) > size)
+ return 0;
+ hdr = CONST_CAST(struct luks2_hdr_disk *) (data + hdr_size1);
+
+ hdr_size2 = be64_to_cpu(hdr->hdr_size);
+ if (hdr_size2 > size || (hdr_size1 + hdr_size2) > size)
+ return 0;
+
+ memset(&hdr->csum, 0, LUKS2_CHECKSUM_L);
+ if ((r = crypt_hash_init(&hd, "sha256")))
+ goto out;
+ if ((r = crypt_hash_write(hd, (char*) hdr, hdr_size2)))
+ goto out;
+ if ((r = crypt_hash_final(hd, (char*)&hdr->csum, (size_t)hash_size)))
+ goto out;
+
+out:
+ if (hd)
+ crypt_hash_destroy(hd);
+ return r;
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ int fd;
+ struct crypt_device *cd = NULL;
+ char name[] = "/tmp/test-script-fuzz.XXXXXX";
+
+ if (calculate_checksum(data, size))
+ return 0;
+
+ fd = mkostemp(name, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
+ if (fd == -1)
+ err(EXIT_FAILURE, "mkostemp() failed");
+
+ /* enlarge header */
+ if (ftruncate(fd, FILESIZE) == -1)
+ goto out;
+
+ if (write_buffer(fd, data, size) != (ssize_t)size)
+ goto out;
+
+ if (crypt_init(&cd, name) == 0)
+ (void)crypt_load(cd, CRYPT_LUKS2, NULL);
+ crypt_free(cd);
+out:
+ close(fd);
+ unlink(name);
+ return 0;
+}
+}
diff --git a/tests/fuzz/crypt2_load_fuzz.dict b/tests/fuzz/crypt2_load_fuzz.dict
new file mode 100644
index 0000000..fedf1a4
--- /dev/null
+++ b/tests/fuzz/crypt2_load_fuzz.dict
@@ -0,0 +1,130 @@
+# LUKS2 dictionary based on AFL dictionary for JSON
+# -------------------------------------------------
+# JSON dictionary from https://github.com/google/AFL/blob/master/dictionaries/json.dict
+# Inspired by a dictionary by Jakub Wilk <jwilk@jwilk.net>
+#
+# LUKS2 keywords by Daniel Zatovic
+
+"0"
+",0"
+":0"
+"0:"
+"-1.2e+3"
+
+"true"
+"false"
+"null"
+
+"\"\""
+",\"\""
+":\"\""
+"\"\":"
+
+"{}"
+",{}"
+":{}"
+"{\"\":0}"
+"{{}}"
+
+"[]"
+",[]"
+":[]"
+"[0]"
+"[[]]"
+
+"''"
+"\\"
+"\\b"
+"\\f"
+"\\n"
+"\\r"
+"\\t"
+"\\u0000"
+"\\x00"
+"\\0"
+"\\uD800\\uDC00"
+"\\uDBFF\\uDFFF"
+
+"\"\":0"
+"//"
+"/**/"
+
+"$ref"
+"type"
+"coordinates"
+"@context"
+"@id"
+
+","
+":"
+
+"1024"
+"2048"
+"4096"
+"512"
+"aegis128-random"
+"aes-cbc:essiv:sha256"
+"aes-xts-plain64"
+"af"
+"allow-discards"
+"area"
+"argon2i"
+"argon2id"
+"backup-final"
+"backup-moved-segment"
+"backup-previous"
+"checksum"
+"config"
+"cpus"
+"crypt"
+"datashift"
+"digest"
+"digests"
+"direction"
+"encryption"
+"flags"
+"hash"
+"in-reencryption"
+"integrity"
+"iterations"
+"iv_tweak"
+"journal"
+"journal_encryption"
+"journal_integrity"
+"json_size"
+"kdf"
+"key_description"
+"key_size"
+"keyslots"
+"keyslots_size"
+"linear"
+"luks2"
+"luks2-keyring"
+"LUKS\xBA\xBE"
+"memory"
+"mode"
+"no-journal"
+"none"
+"no-read-workqueue"
+"no-write-workqueue"
+"offline-reencrypt"
+"offset"
+"online-reencrypt-v2"
+"pbkdf2"
+"priority"
+"raw"
+"reencrypt"
+"requirements"
+"salt"
+"same-cpu-crypt"
+"sector_size"
+"segments"
+"serpent-xts-plain64"
+"shift_size"
+"size"
+"SKUL\xBA\xBE"
+"stripes"
+"submit-from-crypt-cpus"
+"time"
+"tokens"
+"twofish-xts-plain64"
diff --git a/tests/fuzz/crypt2_load_ondisk_fuzz.cc b/tests/fuzz/crypt2_load_ondisk_fuzz.cc
new file mode 100644
index 0000000..9b5328d
--- /dev/null
+++ b/tests/fuzz/crypt2_load_ondisk_fuzz.cc
@@ -0,0 +1,64 @@
+/*
+ * cryptsetup LUKS1, FileVault, BitLocker fuzz target
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+extern "C" {
+#define FILESIZE (16777216)
+#include "src/cryptsetup.h"
+#include <err.h>
+#include "luks1/luks.h"
+#include "crypto_backend/crypto_backend.h"
+#include "FuzzerInterface.h"
+
+void empty_log(int level, const char *msg, void *usrptr) {}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ int fd, r;
+ struct crypt_device *cd = NULL;
+ char name[] = "/tmp/test-script-fuzz.XXXXXX";
+
+ fd = mkostemp(name, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC);
+ if (fd == -1)
+ err(EXIT_FAILURE, "mkostemp() failed");
+
+ /* enlarge header */
+ if (ftruncate(fd, FILESIZE) == -1)
+ goto out;
+
+ if (write_buffer(fd, data, size) != (ssize_t) size)
+ goto out;
+
+ crypt_set_log_callback(NULL, empty_log, NULL);
+
+ if (crypt_init(&cd, name) == 0) {
+ r = crypt_load(cd, CRYPT_LUKS1, NULL);
+ if (r == 0)
+ goto out;
+
+ r = crypt_load(cd, CRYPT_FVAULT2, NULL);
+ if (r == 0)
+ goto out;
+
+ (void) crypt_load(cd, CRYPT_BITLK, NULL);
+ }
+out:
+ crypt_free(cd);
+ close(fd);
+ unlink(name);
+ return 0;
+}
+}
diff --git a/tests/fuzz/crypt2_load_ondisk_fuzz.dict b/tests/fuzz/crypt2_load_ondisk_fuzz.dict
new file mode 100644
index 0000000..3923db5
--- /dev/null
+++ b/tests/fuzz/crypt2_load_ondisk_fuzz.dict
@@ -0,0 +1,9 @@
+"aegis128-random"
+"aes-cbc:essiv:sha256"
+"aes-xts-plain64"
+"aes-lrv-plain64"
+"twofish-xts-plain64"
+"serpent-xts-plain64"
+"whirpool"
+"sha256"
+"sha1"
diff --git a/tests/fuzz/crypt2_load_proto_fuzz.cc b/tests/fuzz/crypt2_load_proto_fuzz.cc
new file mode 100644
index 0000000..498c006
--- /dev/null
+++ b/tests/fuzz/crypt2_load_proto_fuzz.cc
@@ -0,0 +1,51 @@
+/*
+ * cryptsetup LUKS2 custom mutator fuzz target
+ *
+ * Copyright (C) 2022-2023 Daniel Zatovic <daniel.zatovic@gmail.com>
+ * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "LUKS2.pb.h"
+#include "proto_to_luks2_converter.h"
+#include "libfuzzer/libfuzzer_macro.h"
+#include "FuzzerInterface.h"
+
+extern "C" {
+#include <libcryptsetup.h>
+#include <err.h>
+#include <fcntl.h>
+#include <unistd.h>
+}
+
+DEFINE_PROTO_FUZZER(const LUKS2_proto::LUKS2_both_headers &headers) {
+ struct crypt_device *cd = NULL;
+ char name[] = "/tmp/test-proto-fuzz.XXXXXX";
+ int fd = mkostemp(name, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
+
+ if (fd < 0)
+ err(EXIT_FAILURE, "mkostemp() failed");
+
+ LUKS2_proto::LUKS2ProtoConverter converter;
+ converter.convert(headers, fd);
+
+ if (crypt_init(&cd, name) == 0)
+ (void)crypt_load(cd, CRYPT_LUKS2, NULL);
+ crypt_free(cd);
+
+ close(fd);
+ unlink(name);
+}
diff --git a/tests/fuzz/crypt2_load_proto_plain_json_fuzz.cc b/tests/fuzz/crypt2_load_proto_plain_json_fuzz.cc
new file mode 100644
index 0000000..f3565ab
--- /dev/null
+++ b/tests/fuzz/crypt2_load_proto_plain_json_fuzz.cc
@@ -0,0 +1,51 @@
+/*
+ * cryptsetup LUKS2 custom mutator fuzz target
+ *
+ * Copyright (C) 2022-2023 Daniel Zatovic <daniel.zatovic@gmail.com>
+ * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "LUKS2_plain_JSON.pb.h"
+#include "plain_json_proto_to_luks2_converter.h"
+#include "libfuzzer/libfuzzer_macro.h"
+#include "FuzzerInterface.h"
+
+extern "C" {
+#include <libcryptsetup.h>
+#include <err.h>
+#include <fcntl.h>
+#include <unistd.h>
+}
+
+DEFINE_PROTO_FUZZER(const json_proto::LUKS2_both_headers &headers) {
+ struct crypt_device *cd = NULL;
+ char name[] = "/tmp/test-proto-fuzz.XXXXXX";
+ int fd = mkostemp(name, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
+
+ if (fd < 0)
+ err(EXIT_FAILURE, "mkostemp() failed");
+
+ json_proto::LUKS2ProtoConverter converter;
+ converter.convert(headers, fd);
+
+ if (crypt_init(&cd, name) == 0)
+ (void)crypt_load(cd, CRYPT_LUKS2, NULL);
+ crypt_free(cd);
+
+ close(fd);
+ unlink(name);
+}
diff --git a/tests/fuzz/crypt2_load_proto_plain_json_fuzz.dict b/tests/fuzz/crypt2_load_proto_plain_json_fuzz.dict
new file mode 100644
index 0000000..7d83151
--- /dev/null
+++ b/tests/fuzz/crypt2_load_proto_plain_json_fuzz.dict
@@ -0,0 +1,72 @@
+# LUKS2 keywords by Daniel Zatovic
+
+"1024"
+"2048"
+"4096"
+"512"
+"aegis128-random"
+"aes-cbc:essiv:sha256"
+"aes-xts-plain64"
+"af"
+"allow-discards"
+"area"
+"argon2i"
+"argon2id"
+"backup-final"
+"backup-moved-segment"
+"backup-previous"
+"checksum"
+"config"
+"cpus"
+"crypt"
+"datashift"
+"digest"
+"digests"
+"direction"
+"encryption"
+"flags"
+"hash"
+"in-reencryption"
+"integrity"
+"iterations"
+"iv_tweak"
+"journal"
+"journal_encryption"
+"journal_integrity"
+"json_size"
+"kdf"
+"key_description"
+"key_size"
+"keyslots"
+"keyslots_size"
+"linear"
+"luks2"
+"luks2-keyring"
+"LUKS\xBA\xBE"
+"memory"
+"mode"
+"no-journal"
+"none"
+"no-read-workqueue"
+"no-write-workqueue"
+"offline-reencrypt"
+"offset"
+"online-reencrypt-v2"
+"pbkdf2"
+"priority"
+"raw"
+"reencrypt"
+"requirements"
+"salt"
+"same-cpu-crypt"
+"sector_size"
+"segments"
+"serpent-xts-plain64"
+"shift_size"
+"size"
+"SKUL\xBA\xBE"
+"stripes"
+"submit-from-crypt-cpus"
+"time"
+"tokens"
+"twofish-xts-plain64"
diff --git a/tests/fuzz/json_proto_converter.cc b/tests/fuzz/json_proto_converter.cc
new file mode 100644
index 0000000..ed453be
--- /dev/null
+++ b/tests/fuzz/json_proto_converter.cc
@@ -0,0 +1,87 @@
+// Copyright 2020 Google Inc.
+//
+// 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 "json_proto_converter.h"
+
+namespace json_proto {
+
+void JsonProtoConverter::AppendArray(const ArrayValue& array_value) {
+ data_ << '[';
+ bool need_comma = false;
+ for (const auto& value : array_value.value()) {
+ // Trailing comma inside of an array makes JSON invalid, avoid adding that.
+ if (need_comma)
+ data_ << ',';
+ else
+ need_comma = true;
+
+ AppendValue(value);
+ }
+ data_ << ']';
+}
+
+void JsonProtoConverter::AppendNumber(const NumberValue& number_value) {
+ if (number_value.has_float_value()) {
+ data_ << number_value.float_value().value();
+ } else if (number_value.has_exponent_value()) {
+ auto value = number_value.exponent_value();
+ data_ << value.base();
+ data_ << (value.use_uppercase() ? 'E' : 'e');
+ data_ << value.exponent();
+ } else if (number_value.has_exponent_frac_value()) {
+ auto value = number_value.exponent_value();
+ data_ << value.base();
+ data_ << (value.use_uppercase() ? 'E' : 'e');
+ data_ << value.exponent();
+ } else {
+ data_ << number_value.integer_value().value();
+ }
+}
+
+void JsonProtoConverter::AppendObject(const JsonObject& json_object) {
+ data_ << '{' << '"' << json_object.name() << '"' << ':';
+ AppendValue(json_object.value());
+ data_ << '}';
+}
+
+void JsonProtoConverter::AppendValue(const JsonValue& json_value) {
+ if (json_value.has_object_value()) {
+ AppendObject(json_value.object_value());
+ } else if (json_value.has_array_value()) {
+ AppendArray(json_value.array_value());
+ } else if (json_value.has_number_value()) {
+ AppendNumber(json_value.number_value());
+ } else if (json_value.has_string_value()) {
+ data_ << '"' << json_value.string_value().value() << '"';
+ } else if (json_value.has_boolean_value()) {
+ data_ << (json_value.boolean_value().value() ? "true" : "false");
+ } else {
+ data_ << "null";
+ }
+}
+
+std::string JsonProtoConverter::Convert(const JsonObject& json_object) {
+ AppendObject(json_object);
+ return data_.str();
+}
+
+std::string JsonProtoConverter::Convert(
+ const json_proto::ArrayValue& json_array) {
+ AppendArray(json_array);
+ return data_.str();
+}
+
+} // namespace json_proto
diff --git a/tests/fuzz/json_proto_converter.h b/tests/fuzz/json_proto_converter.h
new file mode 100644
index 0000000..ca52d67
--- /dev/null
+++ b/tests/fuzz/json_proto_converter.h
@@ -0,0 +1,43 @@
+// Copyright 2020 Google Inc.
+//
+// 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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef JSON_PROTO_CONVERTER_H_
+#define JSON_PROTO_CONVERTER_H_
+
+#include <sstream>
+#include <string>
+
+#include "LUKS2_plain_JSON.pb.h"
+
+namespace json_proto {
+
+class JsonProtoConverter {
+ public:
+ std::string Convert(const json_proto::JsonObject&);
+ std::string Convert(const json_proto::ArrayValue&);
+
+ private:
+ std::stringstream data_;
+
+ void AppendArray(const json_proto::ArrayValue&);
+ void AppendNumber(const json_proto::NumberValue&);
+ void AppendObject(const json_proto::JsonObject&);
+ void AppendValue(const json_proto::JsonValue&);
+};
+
+} // namespace json_proto
+
+#endif // TESTING_LIBFUZZER_PROTO_JSON_PROTO_CONVERTER_H_
diff --git a/tests/fuzz/oss-fuzz-build.sh b/tests/fuzz/oss-fuzz-build.sh
new file mode 100755
index 0000000..b2f643f
--- /dev/null
+++ b/tests/fuzz/oss-fuzz-build.sh
@@ -0,0 +1,152 @@
+#!/usr/bin/env bash
+
+function in_oss_fuzz()
+{
+ test -n "$FUZZING_ENGINE"
+}
+
+echo "Running cryptsetup OSS-Fuzz build script."
+env
+set -ex
+PWD=$(pwd)
+
+export LC_CTYPE=C.UTF-8
+
+export SRC=${SRC:-$PWD/build}
+export OUT="${OUT:-$PWD/out}"
+export DEPS_PATH=$SRC/static_lib_deps
+
+export PKG_CONFIG_PATH="$DEPS_PATH"/lib/pkgconfig
+
+export CC=${CC:-clang}
+export CXX=${CXX:-clang++}
+export LIB_FUZZING_ENGINE="${LIB_FUZZING_ENGINE:--fsanitize=fuzzer}"
+
+SANITIZER="${SANITIZER:-address -fsanitize-address-use-after-scope}"
+flags="-O1 -fno-omit-frame-pointer -gline-tables-only -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=$SANITIZER -fsanitize=fuzzer-no-link"
+
+export CFLAGS="${CFLAGS:-$flags} -I$DEPS_PATH/include"
+export CXXFLAGS="${CXXFLAGS:-$flags} -I$DEPS_PATH/include"
+export LDFLAGS="${LDFLAGS-} -L$DEPS_PATH/lib"
+
+ENABLED_FUZZERS=${ENABLED_FUZZERS:-crypt2_load_fuzz crypt2_load_ondisk_fuzz crypt2_load_proto_plain_json_fuzz}
+
+mkdir -p $SRC
+mkdir -p $OUT
+mkdir -p $DEPS_PATH
+cd $SRC
+
+LIBFUZZER_PATCH="$PWD/unpoison-mutated-buffers-from-libfuzzer.patch"
+in_oss_fuzz && LIBFUZZER_PATCH="$PWD/cryptsetup/tests/fuzz/unpoison-mutated-buffers-from-libfuzzer.patch"
+
+in_oss_fuzz && apt-get update && apt-get install -y \
+ make autoconf automake autopoint libtool pkg-config \
+ sharutils gettext expect keyutils ninja-build \
+ bison
+
+[ ! -d zlib ] && git clone --depth 1 https://github.com/madler/zlib.git
+[ ! -d xz ] && git clone https://git.tukaani.org/xz.git
+[ ! -d json-c ] && git clone --depth 1 https://github.com/json-c/json-c.git
+[ ! -d lvm2 ] && git clone --depth 1 https://sourceware.org/git/lvm2.git
+[ ! -d popt ] && git clone --depth 1 https://github.com/rpm-software-management/popt.git
+[ ! -d libprotobuf-mutator ] && git clone --depth 1 https://github.com/google/libprotobuf-mutator.git \
+ && [ "$SANITIZER" == "memory" ] && ( cd libprotobuf-mutator; patch -p1 < $LIBFUZZER_PATCH )
+[ ! -d openssl ] && git clone --depth 1 https://github.com/openssl/openssl
+[ ! -d util-linux ] && git clone --depth 1 https://github.com/util-linux/util-linux
+[ ! -d cryptsetup_fuzzing ] && git clone --depth 1 https://gitlab.com/cryptsetup/cryptsetup_fuzzing.git
+
+cd openssl
+./Configure --prefix="$DEPS_PATH" --libdir=lib no-shared no-module no-asm
+make build_generated
+make -j libcrypto.a
+make install_dev
+cd ..
+
+cd util-linux
+./autogen.sh
+./configure --prefix="$DEPS_PATH" --enable-static --disable-shared -disable-all-programs --enable-libuuid --enable-libblkid
+make -j
+make install
+cd ..
+
+cd zlib
+./configure --prefix="$DEPS_PATH" --static
+make -j
+make install
+cd ..
+
+cd xz
+./autogen.sh --no-po4a
+./configure --prefix="$DEPS_PATH" --enable-static --disable-shared
+make -j
+make install
+cd ..
+
+cd json-c
+mkdir -p build
+rm -fr build/*
+cd build
+cmake .. -DCMAKE_INSTALL_PREFIX="$DEPS_PATH" -DBUILD_SHARED_LIBS=OFF -DBUILD_STATIC_LIBS=ON
+make -j
+make install
+cd ../..
+
+cd lvm2
+./configure --prefix="$DEPS_PATH" --enable-static_link --disable-udev_sync --enable-pkgconfig --disable-selinux
+make -j libdm.device-mapper
+# build of dmsetup.static is broken
+# make install_device-mapper
+cp ./libdm/ioctl/libdevmapper.a "$DEPS_PATH"/lib/
+cp ./libdm/libdevmapper.h "$DEPS_PATH"/include/
+cp ./libdm/libdevmapper.pc "$PKG_CONFIG_PATH"
+cd ..
+
+cd popt
+# --no-undefined is incompatible with sanitizers
+sed -i -e 's/-Wl,--no-undefined //' src/CMakeLists.txt
+mkdir -p build
+rm -fr build/*
+cd build
+cmake .. -DCMAKE_INSTALL_PREFIX="$DEPS_PATH" -DBUILD_SHARED_LIBS=OFF
+make -j
+make install
+cd ../..
+
+cd libprotobuf-mutator
+mkdir -p build
+rm -fr build/*
+cd build
+cmake .. -GNinja \
+ -DCMAKE_INSTALL_PREFIX="$DEPS_PATH" \
+ -DPKG_CONFIG_PATH="$PKG_CONFIG_PATH" \
+ -DLIB_PROTO_MUTATOR_TESTING=OFF \
+ -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON
+ninja
+ninja install
+cd external.protobuf;
+cp -Rf bin lib include "$DEPS_PATH";
+cd ../../..
+
+if in_oss_fuzz; then
+ mkdir -p cryptsetup/tests/fuzz/build
+ ln -s ../../../../static_lib_deps cryptsetup/tests/fuzz/build/static_lib_deps
+ cd cryptsetup
+else
+ cd ../../..
+fi
+./autogen.sh
+./configure --enable-static --disable-asciidoc --disable-ssh-token --disable-udev --disable-selinux --with-crypto_backend=openssl --disable-shared --enable-fuzz-targets
+make clean
+make -j fuzz-targets
+
+for fuzzer in $ENABLED_FUZZERS; do
+ cp tests/fuzz/$fuzzer $OUT
+ cp $SRC/cryptsetup_fuzzing/${fuzzer}_seed_corpus.zip $OUT
+
+ # optionally copy the dictionary if it exists
+ if [ -e tests/fuzz/${fuzzer}.dict ]; then
+ cp tests/fuzz/${fuzzer}.dict $OUT
+ fi
+done
+
+cd $PWD
diff --git a/tests/fuzz/plain_json_proto_to_luks2.cc b/tests/fuzz/plain_json_proto_to_luks2.cc
new file mode 100644
index 0000000..8c56c15
--- /dev/null
+++ b/tests/fuzz/plain_json_proto_to_luks2.cc
@@ -0,0 +1,75 @@
+/*
+ * cryptsetup LUKS2 protobuf to image converter
+ *
+ * Copyright (C) 2022-2023 Daniel Zatovic <daniel.zatovic@gmail.com>
+ * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <iostream>
+#include <string>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <google/protobuf/text_format.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+
+#include "plain_json_proto_to_luks2_converter.h"
+
+using namespace json_proto;
+
+int main(int argc, char *argv[]) {
+ LUKS2_both_headers headers;
+ LUKS2ProtoConverter converter;
+ int fd;
+
+ std::string out_img_name;
+
+ if (argc != 2) {
+ std::cerr << "Usage: " << argv[0] << " <LUKS2 proto>\n";
+ return EXIT_FAILURE;
+ }
+
+ fd = open(argv[1], O_RDONLY);
+ if (fd < 0) {
+ std::cerr << "Failed to open " << argv[1] << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ google::protobuf::io::FileInputStream fileInput(fd);
+
+ if (!google::protobuf::TextFormat::Parse(&fileInput, &headers)) {
+ std::cerr << "Failed to parse protobuf " << argv[1] << std::endl;
+ close(fd);
+ return EXIT_FAILURE;
+ }
+ close(fd);
+
+ out_img_name = argv[1];
+ out_img_name += ".img";
+
+ fd = open(out_img_name.c_str(), O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC|O_TRUNC, 0644);
+ if (fd < 0) {
+ std::cerr << "Failed to open output file " << out_img_name << std::endl;
+ return EXIT_FAILURE;
+ }
+ converter.set_write_headers_only(false);
+ converter.convert(headers, fd);
+
+ close(fd);
+ return EXIT_SUCCESS;
+}
diff --git a/tests/fuzz/plain_json_proto_to_luks2_converter.cc b/tests/fuzz/plain_json_proto_to_luks2_converter.cc
new file mode 100644
index 0000000..823c0c5
--- /dev/null
+++ b/tests/fuzz/plain_json_proto_to_luks2_converter.cc
@@ -0,0 +1,153 @@
+/*
+ * cryptsetup LUKS2 custom mutator fuzz target
+ *
+ * Copyright (C) 2022-2023 Daniel Zatovic <daniel.zatovic@gmail.com>
+ * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "plain_json_proto_to_luks2_converter.h"
+#include "json_proto_converter.h"
+
+extern "C" {
+#include "src/cryptsetup.h"
+#include "luks2/luks2.h"
+#include <err.h>
+}
+
+namespace json_proto {
+
+void LUKS2ProtoConverter::emit_luks2_binary_header(const LUKS2_header &header_proto, int fd, uint64_t offset, uint64_t seqid, const std::string &json_text) {
+ struct luks2_hdr_disk hdr = {};
+ int r;
+
+ if (hd)
+ crypt_hash_destroy(hd);
+ if (crypt_hash_init(&hd, "sha256"))
+ err(EXIT_FAILURE, "crypt_hash_init failed");
+
+
+ r = lseek(fd, offset, SEEK_SET);
+ if (r == -1)
+ err(EXIT_FAILURE, "lseek failed");
+
+ switch (header_proto.magic()) {
+ case INVALID:
+ memset(&hdr.magic, 0, LUKS2_MAGIC_L);
+ break;
+ case FIRST:
+ memcpy(&hdr.magic, LUKS2_MAGIC_1ST, LUKS2_MAGIC_L);
+ break;
+ case SECOND:
+ memcpy(&hdr.magic, LUKS2_MAGIC_2ND, LUKS2_MAGIC_L);
+ break;
+ }
+ hdr.version = cpu_to_be16(header_proto.version());
+ hdr.hdr_size = cpu_to_be64(header_proto.hdr_size());
+ hdr.seqid = cpu_to_be64(seqid);
+ strncpy(hdr.checksum_alg, "sha256", LUKS2_CHECKSUM_ALG_L);
+ hdr.checksum_alg[LUKS2_CHECKSUM_ALG_L - 1] = '\0';
+ strncpy(hdr.uuid, "af7f64ea-3233-4581-946b-6187d812841e", LUKS2_UUID_L);
+ memset(hdr.salt, 1, LUKS2_SALT_L);
+
+
+ if (header_proto.has_selected_offset())
+ hdr.hdr_offset = cpu_to_be64(header_proto.selected_offset());
+ else
+ hdr.hdr_offset = cpu_to_be64(offset);
+
+ if (write_buffer(fd, &hdr, LUKS2_HDR_BIN_LEN) != LUKS2_HDR_BIN_LEN)
+ err(EXIT_FAILURE, "write_buffer failed");
+ if (crypt_hash_write(hd, (char*)&hdr, LUKS2_HDR_BIN_LEN))
+ err(EXIT_FAILURE, "crypt_hash_write failed");
+
+ size_t hdr_json_area_len = header_proto.hdr_size() - LUKS2_HDR_BIN_LEN;
+ uint8_t csum[LUKS2_CHECKSUM_L];
+
+ size_t write_size = json_text.length() > hdr_json_area_len - 1 ? hdr_json_area_len - 1 : json_text.length();
+ if (write_buffer(fd, json_text.c_str(), write_size) != (ssize_t)write_size)
+ err(EXIT_FAILURE, "write_buffer failed");
+ if (crypt_hash_write(hd, json_text.c_str(), write_size))
+ err(EXIT_FAILURE, "crypt_hash_write failed");
+
+ for (size_t i = 0; i < (hdr_json_area_len - write_size); i++) {
+ if (crypt_hash_write(hd, "\0", 1))
+ err(EXIT_FAILURE, "crypt_hash_write failed");
+ }
+
+ if (header_proto.use_correct_checksum()) {
+ if (lseek(fd, offset + offsetof(luks2_hdr_disk, csum), SEEK_SET) == -1)
+ err(EXIT_FAILURE, "lseek failed");
+
+ int hash_size = crypt_hash_size("sha256");
+ if (hash_size <= 0)
+ err(EXIT_FAILURE, "crypt_hash_size failed");
+
+ if (crypt_hash_final(hd, (char*)csum, (size_t)hash_size))
+ err(EXIT_FAILURE, "crypt_hash_final failed");
+ if (write_buffer(fd, csum, hash_size) != hash_size)
+ err(EXIT_FAILURE, "write_buffer failed");
+ }
+}
+
+void LUKS2ProtoConverter::set_write_headers_only(bool headers_only) {
+ write_headers_only = headers_only;
+}
+
+void LUKS2ProtoConverter::convert(const LUKS2_both_headers &headers, int fd) {
+ uint64_t primary_seqid, secondary_seqid;
+ int result;
+
+ size_t out_size = headers.primary_header().hdr_size() + headers.secondary_header().hdr_size();
+
+ if (!write_headers_only)
+ out_size += KEYSLOTS_SIZE + DATA_SIZE;
+
+ result = ftruncate(fd, out_size);
+ if (result == -1)
+ err(EXIT_FAILURE, "truncate failed");
+
+ result = lseek(fd, 0, SEEK_SET);
+ if (result == -1)
+ err(EXIT_FAILURE, "lseek failed");
+
+ switch (headers.seqid()) {
+ case EQUAL:
+ primary_seqid = 1;
+ secondary_seqid = 1;
+ break;
+ case PRIMARY_GREATER:
+ primary_seqid = 2;
+ secondary_seqid = 1;
+ break;
+ case SECONDARY_GREATER:
+ primary_seqid = 1;
+ secondary_seqid = 2;
+ break;
+ }
+
+ JsonProtoConverter converter;
+ std::string json_text = converter.Convert(headers.json_area());
+
+ emit_luks2_binary_header(headers.primary_header(), fd, 0, primary_seqid, json_text);
+ emit_luks2_binary_header(headers.secondary_header(), fd, headers.primary_header().hdr_size(), secondary_seqid, json_text);
+}
+
+LUKS2ProtoConverter::~LUKS2ProtoConverter() {
+ if (hd)
+ crypt_hash_destroy(hd);
+}
+} // namespace LUKS2_proto
diff --git a/tests/fuzz/plain_json_proto_to_luks2_converter.h b/tests/fuzz/plain_json_proto_to_luks2_converter.h
new file mode 100644
index 0000000..7decf9f
--- /dev/null
+++ b/tests/fuzz/plain_json_proto_to_luks2_converter.h
@@ -0,0 +1,58 @@
+/*
+ * cryptsetup LUKS2 custom mutator fuzz target
+ *
+ * Copyright (C) 2022-2023 Daniel Zatovic <daniel.zatovic@gmail.com>
+ * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef LUKS2_PROTO_CONVERTER_H_
+#define LUKS2_PROTO_CONVERTER_H_
+
+#include <sstream>
+#include <string>
+#include <json-c/json.h>
+
+#include "LUKS2_plain_JSON.pb.h"
+extern "C" {
+#include "crypto_backend/crypto_backend.h"
+}
+
+namespace json_proto {
+
+class LUKS2ProtoConverter {
+ public:
+ ~LUKS2ProtoConverter();
+ void create_jobj(const LUKS2_both_headers &headers, uint64_t hdr_size);
+ void convert(const LUKS2_both_headers &headers, int fd);
+ void create_jobj(const LUKS2_both_headers &headers);
+ void emit_luks2_binary_header(const LUKS2_header &header_proto, int fd, uint64_t offset, uint64_t seqid, const std::string &json_text);
+
+ void set_write_headers_only(bool headers_only);
+
+ const uint8_t *get_out_buffer();
+ size_t get_out_size();
+
+ static const uint64_t KEYSLOTS_SIZE = 3 * 1024 * 1024;
+ static const uint64_t DATA_SIZE = 16 * 1024 * 1024;
+ private:
+ bool write_headers_only = false;
+ struct crypt_hash *hd = NULL;
+};
+
+} // namespace LUKS2_proto
+
+#endif // LUKS2_PROTO_CONVERTER_H_
diff --git a/tests/fuzz/proto_to_luks2.cc b/tests/fuzz/proto_to_luks2.cc
new file mode 100644
index 0000000..4a27cad
--- /dev/null
+++ b/tests/fuzz/proto_to_luks2.cc
@@ -0,0 +1,75 @@
+/*
+ * cryptsetup LUKS2 protobuf to image converter
+ *
+ * Copyright (C) 2022-2023 Daniel Zatovic <daniel.zatovic@gmail.com>
+ * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <iostream>
+#include <string>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <google/protobuf/text_format.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+
+#include "proto_to_luks2_converter.h"
+
+using namespace LUKS2_proto;
+
+int main(int argc, char *argv[]) {
+ LUKS2_both_headers headers;
+ LUKS2ProtoConverter converter;
+ int fd;
+
+ std::string out_img_name;
+
+ if (argc != 2) {
+ std::cerr << "Usage: " << argv[0] << " <LUKS2 proto>\n";
+ return EXIT_FAILURE;
+ }
+
+ fd = open(argv[1], O_RDONLY);
+ if (fd < 0) {
+ std::cerr << "Failed to open " << argv[1] << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ google::protobuf::io::FileInputStream fileInput(fd);
+
+ if (!google::protobuf::TextFormat::Parse(&fileInput, &headers)) {
+ std::cerr << "Failed to parse protobuf " << argv[1] << std::endl;
+ close(fd);
+ return EXIT_FAILURE;
+ }
+ close(fd);
+
+ out_img_name = argv[1];
+ out_img_name += ".img";
+
+ fd = open(out_img_name.c_str(), O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC|O_TRUNC, 0644);
+ if (fd < 0) {
+ std::cerr << "Failed to open output file " << out_img_name << std::endl;
+ return EXIT_FAILURE;
+ }
+ converter.set_write_headers_only(false);
+ converter.convert(headers, fd);
+
+ close(fd);
+ return EXIT_SUCCESS;
+}
diff --git a/tests/fuzz/proto_to_luks2_converter.cc b/tests/fuzz/proto_to_luks2_converter.cc
new file mode 100644
index 0000000..96a70b7
--- /dev/null
+++ b/tests/fuzz/proto_to_luks2_converter.cc
@@ -0,0 +1,604 @@
+/*
+ * cryptsetup LUKS2 custom mutator fuzz target
+ *
+ * Copyright (C) 2022-2023 Daniel Zatovic <daniel.zatovic@gmail.com>
+ * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "proto_to_luks2_converter.h"
+#include <iostream>
+
+extern "C" {
+#include "src/cryptsetup.h"
+#include "luks2/luks2.h"
+#include <err.h>
+}
+
+namespace LUKS2_proto {
+
+std::string LUKS2ProtoConverter::string_uint64_to_string(const string_uint64 &str_u64) {
+ std::ostringstream os;
+
+ if (str_u64.negative())
+ os << "-";
+
+ if (str_u64.has_uint_num())
+ os << str_u64.uint_num();
+ else if (str_u64.has_string_num())
+ os << str_u64.string_num();
+
+ return os.str();
+}
+
+std::string LUKS2ProtoConverter::object_id_to_string(const object_id &oid) {
+ std::ostringstream os;
+
+ if (oid.has_int_id()) {
+ os << (oid.int_id() % 33) - 16;
+ } else if (oid.has_string_id()) {
+ os << oid.string_id();
+ }
+
+ return os.str();
+}
+
+std::string LUKS2ProtoConverter::hash_algorithm_to_string(const hash_algorithm type) {
+ switch (type) {
+ case HASH_ALG_SHA1:
+ return "sha1";
+ case HASH_ALG_SHA256:
+ return "sha256";
+ }
+}
+
+std::string LUKS2ProtoConverter::keyslot_area_type_to_string(const keyslot_area_type type) {
+ switch (type) {
+ case KEYSLOT_AREA_TYPE_RAW:
+ return "raw";
+ case KEYSLOT_AREA_TYPE_NONE:
+ return "none";
+ case KEYSLOT_AREA_TYPE_JOURNAL:
+ return "journal";
+ case KEYSLOT_AREA_TYPE_CHECKSUM:
+ return "checksum";
+ case KEYSLOT_AREA_TYPE_DATASHIFT:
+ return "datashift";
+ }
+}
+
+void LUKS2ProtoConverter::generate_keyslot_area(struct json_object *jobj_area, const keyslot_area_description &keyslot_area_desc) {
+ // mandatory fields
+ if (keyslot_area_desc.has_type())
+ json_object_object_add(jobj_area, "type", json_object_new_string(keyslot_area_type_to_string(keyslot_area_desc.type()).c_str()));
+ if (keyslot_area_desc.has_offset())
+ json_object_object_add(jobj_area, "offset", json_object_new_string(string_uint64_to_string(keyslot_area_desc.offset()).c_str()));
+ if (keyslot_area_desc.has_size())
+ json_object_object_add(jobj_area, "size", json_object_new_string(string_uint64_to_string(keyslot_area_desc.size()).c_str()));
+
+ // raw type fields
+ if (keyslot_area_desc.has_encryption())
+ json_object_object_add(jobj_area, "encryption", json_object_new_string(keyslot_area_desc.encryption().c_str()));
+ if (keyslot_area_desc.has_key_size())
+ json_object_object_add(jobj_area, "key_size", json_object_new_int(keyslot_area_desc.key_size()));
+
+ // checksum type fields
+ if (keyslot_area_desc.has_hash())
+ json_object_object_add(jobj_area, "hash", json_object_new_string(hash_algorithm_to_string(keyslot_area_desc.hash()).c_str()));
+ if (keyslot_area_desc.has_sector_size())
+ json_object_object_add(jobj_area, "sector_size", json_object_new_int(keyslot_area_desc.sector_size()));
+
+ // datashift type fields
+ if (keyslot_area_desc.has_shift_size())
+ json_object_object_add(jobj_area, "shift_size", json_object_new_string(string_uint64_to_string(keyslot_area_desc.shift_size()).c_str()));
+}
+
+std::string LUKS2ProtoConverter::keyslot_kdf_type_to_string(const keyslot_kdf_type type) {
+ switch (type) {
+ case KEYSLOT_KDF_TYPE_PBKDF2:
+ return "pbkdf2";
+ case KEYSLOT_KDF_TYPE_ARGON2I:
+ return "argon2i";
+ case KEYSLOT_KDF_TYPE_ARGON2ID:
+ return "argon2id";
+ }
+}
+
+void LUKS2ProtoConverter::generate_keyslot_kdf(struct json_object *jobj_kdf, const keyslot_kdf_description &keyslot_kdf_desc) {
+ // mandatory fields
+ if (keyslot_kdf_desc.has_type())
+ json_object_object_add(jobj_kdf, "type", json_object_new_string(keyslot_kdf_type_to_string(keyslot_kdf_desc.type()).c_str()));
+
+ if (keyslot_kdf_desc.has_salt())
+ json_object_object_add(jobj_kdf, "salt", json_object_new_string(keyslot_kdf_desc.salt().c_str()));
+ else
+ json_object_object_add(jobj_kdf, "salt", json_object_new_string("6vz4xK7cjan92rDA5JF8O6Jk2HouV0O8DMB6GlztVk="));
+
+ // pbkdf2 type
+ if (keyslot_kdf_desc.has_hash())
+ json_object_object_add(jobj_kdf, "hash", json_object_new_string(hash_algorithm_to_string(keyslot_kdf_desc.hash()).c_str()));
+ if (keyslot_kdf_desc.has_iterations())
+ json_object_object_add(jobj_kdf, "iterations", json_object_new_int(keyslot_kdf_desc.iterations()));
+
+ // argon2i and argon2id types
+ if (keyslot_kdf_desc.has_time())
+ json_object_object_add(jobj_kdf, "time", json_object_new_int(keyslot_kdf_desc.time()));
+ if (keyslot_kdf_desc.has_memory())
+ json_object_object_add(jobj_kdf, "memory", json_object_new_int(keyslot_kdf_desc.memory()));
+ if (keyslot_kdf_desc.has_cpus())
+ json_object_object_add(jobj_kdf, "cpus", json_object_new_int(keyslot_kdf_desc.cpus()));
+}
+
+std::string LUKS2ProtoConverter::keyslot_af_type_to_string(const keyslot_af_type type) {
+ switch (type) {
+ case KEYSLOT_AF_TYPE_LUKS1:
+ return "luks1";
+ }
+}
+
+void LUKS2ProtoConverter::generate_keyslot_af(struct json_object *jobj_af, const keyslot_af_description &keyslot_af_desc) {
+ if (keyslot_af_desc.has_type())
+ json_object_object_add(jobj_af, "type", json_object_new_string(keyslot_af_type_to_string(keyslot_af_desc.type()).c_str()));
+ if (keyslot_af_desc.has_stripes())
+ json_object_object_add(jobj_af, "stripes", json_object_new_int(keyslot_af_desc.stripes()));
+ if (keyslot_af_desc.has_hash())
+ json_object_object_add(jobj_af, "hash", json_object_new_string(hash_algorithm_to_string(keyslot_af_desc.hash()).c_str()));
+}
+
+std::string LUKS2ProtoConverter::keyslot_type_to_string(const keyslot_type type) {
+ switch (type) {
+ case KEYSLOT_TYPE_LUKS2:
+ return "luks2";
+ case KEYSLOT_TYPE_REENCRYPT:
+ return "reencrypt";
+ case KEYSLOT_TYPE_PLACEHOLDER:
+ return "placeholder";
+ }
+}
+
+std::string LUKS2ProtoConverter::reencrypt_keyslot_mode_to_string(const reencrypt_keyslot_mode mode) {
+ switch (mode) {
+ case MODE_REENCRYPT:
+ return "reencrypt";
+ case MODE_ENCRYPT:
+ return "encrypt";
+ case MODE_DECRYPT:
+ return "decrypt";
+ }
+}
+
+std::string LUKS2ProtoConverter::reencrypt_keyslot_direction_to_string(const reencrypt_keyslot_direction direction) {
+ switch (direction) {
+ case DIRECTION_FORWARD:
+ return "forward";
+ case DIRECTION_BACKWARD:
+ return "backward";
+ }
+}
+
+void LUKS2ProtoConverter::generate_keyslot(struct json_object *jobj_keyslots, const keyslot_description &keyslot_desc) {
+ struct json_object *jobj_keyslot, *jobj_area, *jobj_kdf, *jobj_af;
+
+ jobj_keyslot = json_object_new_object();
+ if (keyslot_desc.has_type())
+ json_object_object_add(jobj_keyslot, "type", json_object_new_string(keyslot_type_to_string(keyslot_desc.type()).c_str()));
+ if (keyslot_desc.has_key_size())
+ json_object_object_add(jobj_keyslot, "key_size", json_object_new_int(keyslot_desc.key_size()));
+ if (keyslot_desc.has_priority())
+ json_object_object_add(jobj_keyslot, "priority", json_object_new_int(keyslot_desc.priority()));
+ if (keyslot_desc.has_mode())
+ json_object_object_add(jobj_keyslot, "mode", json_object_new_int(keyslot_desc.mode()));
+ if (keyslot_desc.has_direction())
+ json_object_object_add(jobj_keyslot, "direction", json_object_new_int(keyslot_desc.direction()));
+
+ /* Area object */
+ if (keyslot_desc.has_area()) {
+ jobj_area = json_object_new_object();
+ generate_keyslot_area(jobj_area, keyslot_desc.area());
+ json_object_object_add(jobj_keyslot, "area", jobj_area);
+ }
+
+ /* KDF object */
+ if (keyslot_desc.has_kdf()) {
+ jobj_kdf = json_object_new_object();
+ generate_keyslot_kdf(jobj_kdf, keyslot_desc.kdf());
+ json_object_object_add(jobj_keyslot, "kdf", jobj_kdf);
+ }
+
+ /* AF object */
+ if (keyslot_desc.has_af()) {
+ jobj_af = json_object_new_object();
+ generate_keyslot_af(jobj_af, keyslot_desc.af());
+ json_object_object_add(jobj_keyslot, "af", jobj_af);
+ }
+
+ json_object_object_add(jobj_keyslots, object_id_to_string(keyslot_desc.oid()).c_str(), jobj_keyslot);
+}
+
+void LUKS2ProtoConverter::generate_token(struct json_object *jobj_tokens, const token_description &token_desc) {
+ struct json_object *jobj_token, *jobj_keyslots;
+ jobj_token = json_object_new_object();
+
+ if (token_desc.has_type())
+ json_object_object_add(jobj_token, "type", json_object_new_string(token_desc.type().c_str()));
+
+ if (token_desc.has_key_description())
+ json_object_object_add(jobj_token, "key_description", json_object_new_string(token_desc.key_description().c_str()));
+
+ if (!token_desc.keyslots().empty()) {
+ jobj_keyslots = json_object_new_array();
+
+ for (const object_id& oid : token_desc.keyslots()) {
+ json_object_array_add(jobj_keyslots,
+ json_object_new_string(object_id_to_string(oid).c_str()));
+ }
+
+ /* Replace or add new keyslots array */
+ json_object_object_add(jobj_token, "keyslots", jobj_keyslots);
+ }
+
+ json_object_object_add(jobj_tokens, object_id_to_string(token_desc.oid()).c_str(), jobj_token);
+}
+
+void LUKS2ProtoConverter::generate_digest(struct json_object *jobj_digests, const digest_description &digest_desc) {
+ struct json_object *jobj_digest, *jobj_keyslots, *jobj_segments;
+
+ jobj_digest = json_object_new_object();
+
+ if (digest_desc.has_type())
+ json_object_object_add(jobj_digest, "type", json_object_new_string(keyslot_kdf_type_to_string(digest_desc.type()).c_str()));
+
+ if (!digest_desc.keyslots().empty()) {
+ jobj_keyslots = json_object_new_array();
+
+ for (const object_id& oid : digest_desc.keyslots()) {
+ json_object_array_add(jobj_keyslots,
+ json_object_new_string(object_id_to_string(oid).c_str()));
+ }
+
+ /* Replace or add new keyslots array */
+ json_object_object_add(jobj_digest, "keyslots", jobj_keyslots);
+ }
+
+ if (!digest_desc.segments().empty()) {
+ jobj_segments = json_object_new_array();
+
+ for (const object_id& oid : digest_desc.segments()) {
+ json_object_array_add(jobj_segments,
+ json_object_new_string(object_id_to_string(oid).c_str()));
+ }
+
+ /* Replace or add new segments array */
+ json_object_object_add(jobj_digest, "segments", jobj_segments);
+ }
+
+ if (digest_desc.has_salt())
+ json_object_object_add(jobj_digest, "salt", json_object_new_string(digest_desc.salt().c_str()));
+ if (digest_desc.has_digest())
+ json_object_object_add(jobj_digest, "digest", json_object_new_string(digest_desc.digest().c_str()));
+ if (digest_desc.has_hash())
+ json_object_object_add(jobj_digest, "hash", json_object_new_string(hash_algorithm_to_string(digest_desc.hash()).c_str()));
+ if (digest_desc.has_iterations())
+ json_object_object_add(jobj_digest, "iterations", json_object_new_int(digest_desc.iterations()));
+
+ json_object_object_add(jobj_digests, object_id_to_string(digest_desc.oid()).c_str(), jobj_digest);
+}
+
+std::string LUKS2ProtoConverter::segment_type_to_string(segment_type type) {
+ switch (type) {
+ case SEGMENT_TYPE_LINEAR:
+ return "linear";
+ case SEGMENT_TYPE_CRYPT:
+ return "crypt";
+ }
+}
+
+std::string LUKS2ProtoConverter::segment_flag_to_string(segment_flag flag) {
+ switch (flag) {
+ case IN_REENCRYPTION:
+ return "in-reencryption";
+ case BACKUP_FINAL:
+ return "backup-final";
+ case BACKUP_PREVIOUS:
+ return "backup-previous";
+ case BACKUP_MOVED_SEGMENT:
+ return "backup-moved-segment";
+ }
+}
+
+void LUKS2ProtoConverter::generate_segment_integrity(struct json_object *jobj_integrity, const segment_integrity_description &segment_integrity_desc) {
+ if (segment_integrity_desc.has_type())
+ json_object_object_add(jobj_integrity, "type", json_object_new_string(segment_integrity_desc.type().c_str()));
+ if (segment_integrity_desc.has_journal_encryption())
+ json_object_object_add(jobj_integrity, "journal_encryption", json_object_new_string(segment_integrity_desc.journal_encryption().c_str()));
+ if (segment_integrity_desc.has_journal_integrity())
+ json_object_object_add(jobj_integrity, "journal_integrity", json_object_new_string(segment_integrity_desc.journal_integrity().c_str()));
+}
+
+void LUKS2ProtoConverter::generate_segment(struct json_object *jobj_segments, const segment_description &segment_desc) {
+ json_object *jobj_flags, *jobj_integrity;
+ json_object *jobj_segment = json_object_new_object();
+
+ if (segment_desc.has_type())
+ json_object_object_add(jobj_segment, "type", json_object_new_string(segment_type_to_string(segment_desc.type()).c_str()));
+
+ if (segment_desc.has_offset())
+ json_object_object_add(jobj_segment, "offset", json_object_new_string(string_uint64_to_string(segment_desc.offset()).c_str()));
+ if (segment_desc.has_size())
+ json_object_object_add(jobj_segment, "size", json_object_new_string(string_uint64_to_string(segment_desc.size()).c_str()));
+
+ if (!segment_desc.flags().empty()) {
+ jobj_flags = json_object_new_array();
+
+ for (const int flag : segment_desc.flags()) {
+ json_object_array_add(jobj_flags,
+ json_object_new_string(segment_flag_to_string(segment_flag(flag)).c_str()));
+ }
+
+ /* Replace or add new flags array */
+ json_object_object_add(jobj_segment, "flags", jobj_flags);
+ }
+
+ if (segment_desc.has_iv_tweak())
+ json_object_object_add(jobj_segment, "iv_tweak", json_object_new_string(string_uint64_to_string(segment_desc.iv_tweak()).c_str()));
+ if (segment_desc.has_encryption())
+ json_object_object_add(jobj_segment, "encryption", json_object_new_string(segment_desc.encryption().c_str()));
+ if (segment_desc.has_sector_size())
+ json_object_object_add(jobj_segment, "sector_size", json_object_new_int(segment_desc.sector_size()));
+
+ if (segment_desc.has_integrity()) {
+ jobj_integrity = json_object_new_object();
+ generate_segment_integrity(jobj_integrity, segment_desc.integrity());
+ json_object_object_add(jobj_segment, "integrity", jobj_integrity);
+ }
+
+ json_object_object_add(jobj_segments, object_id_to_string(segment_desc.oid()).c_str(), jobj_segment);
+}
+
+void LUKS2ProtoConverter::create_jobj(const LUKS2_both_headers &headers) {
+ json_object *jobj_keyslots = NULL;
+ json_object *jobj_digests = NULL;
+ json_object *jobj_segments = NULL;
+ json_object *jobj_tokens = NULL;
+
+ const json_area_description &json_desc = headers.json_area();
+
+ jobj = json_object_new_object();
+ if (!jobj)
+ return;
+
+ jobj_keyslots = json_object_new_object();
+ for (const keyslot_description &keyslot_desc : json_desc.keyslots()) {
+ generate_keyslot(jobj_keyslots, keyslot_desc);
+ }
+ json_object_object_add(jobj, "keyslots", jobj_keyslots);
+
+ jobj_digests = json_object_new_object();
+ for (const digest_description &digest_desc : json_desc.digests()) {
+ generate_digest(jobj_digests, digest_desc);
+ }
+ json_object_object_add(jobj, "digests", jobj_digests);
+
+ jobj_segments = json_object_new_object();
+ for (const segment_description &segment_desc : json_desc.segments()) {
+ generate_segment(jobj_segments, segment_desc);
+ }
+ json_object_object_add(jobj, "segments", jobj_segments);
+
+ jobj_tokens = json_object_new_object();
+ for (const token_description &token_desc : json_desc.tokens()) {
+ generate_token(jobj_tokens, token_desc);
+ }
+ json_object_object_add(jobj, "tokens", jobj_tokens);
+
+ if (json_desc.has_config()) {
+ uint64_t hdr_size = json_desc.config().use_primary_hdr_size() ? headers.primary_header().hdr_size() : headers.secondary_header().hdr_size();
+ generate_config(json_desc.config(), hdr_size - LUKS2_HDR_BIN_LEN, KEYSLOTS_SIZE);
+ }
+}
+
+void LUKS2ProtoConverter::emit_luks2_binary_header(const LUKS2_header &header_proto, int fd, uint64_t offset, uint64_t seqid) {
+ struct luks2_hdr_disk hdr = {};
+ int r;
+
+ if (hd)
+ crypt_hash_destroy(hd);
+ if (crypt_hash_init(&hd, "sha256"))
+ err(EXIT_FAILURE, "crypt_hash_init failed");
+
+
+ r = lseek(fd, offset, SEEK_SET);
+ if (r == -1)
+ err(EXIT_FAILURE, "lseek failed");
+
+ switch (header_proto.magic()) {
+ case INVALID:
+ memset(&hdr.magic, 0, LUKS2_MAGIC_L);
+ break;
+ case FIRST:
+ memcpy(&hdr.magic, LUKS2_MAGIC_1ST, LUKS2_MAGIC_L);
+ break;
+ case SECOND:
+ memcpy(&hdr.magic, LUKS2_MAGIC_2ND, LUKS2_MAGIC_L);
+ break;
+ }
+ hdr.version = cpu_to_be16(header_proto.version());
+ hdr.hdr_size = cpu_to_be64(header_proto.hdr_size());
+ hdr.seqid = cpu_to_be64(seqid);
+ strncpy(hdr.checksum_alg, "sha256", LUKS2_CHECKSUM_ALG_L);
+ hdr.checksum_alg[LUKS2_CHECKSUM_ALG_L - 1] = '\0';
+ strncpy(hdr.uuid, "af7f64ea-3233-4581-946b-6187d812841e", LUKS2_UUID_L);
+ memset(hdr.salt, 1, LUKS2_SALT_L);
+
+
+ if (header_proto.has_selected_offset())
+ hdr.hdr_offset = cpu_to_be64(header_proto.selected_offset());
+ else
+ hdr.hdr_offset = cpu_to_be64(offset);
+
+ if (write_buffer(fd, &hdr, LUKS2_HDR_BIN_LEN) != LUKS2_HDR_BIN_LEN)
+ err(EXIT_FAILURE, "write_buffer failed");
+ if (crypt_hash_write(hd, (char*)&hdr, LUKS2_HDR_BIN_LEN))
+ err(EXIT_FAILURE, "crypt_hash_write failed");
+
+ size_t hdr_json_area_len = header_proto.hdr_size() - LUKS2_HDR_BIN_LEN;
+ size_t json_text_len;
+ const char *json_text;
+ uint8_t csum[LUKS2_CHECKSUM_L];
+
+ if (jobj) {
+ json_text = json_object_to_json_string_ext((struct json_object *)jobj, JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE);
+ if (!json_text || !*json_text)
+ err(EXIT_FAILURE, "json_object_to_json_string_ext failed");
+
+ json_text_len = strlen(json_text);
+
+ size_t write_size = json_text_len > hdr_json_area_len - 1 ? hdr_json_area_len - 1 : json_text_len;
+ if (write_buffer(fd, json_text, write_size) != (ssize_t)write_size)
+ err(EXIT_FAILURE, "write_buffer failed");
+ if (crypt_hash_write(hd, json_text, write_size))
+ err(EXIT_FAILURE, "crypt_hash_write failed");
+
+ for (size_t i = 0; i < (hdr_json_area_len - write_size); i++) {
+ if (crypt_hash_write(hd, "\0", 1))
+ err(EXIT_FAILURE, "crypt_hash_write failed");
+ }
+ }
+
+ if (header_proto.use_correct_checksum()) {
+ if (lseek(fd, offset + offsetof(luks2_hdr_disk, csum), SEEK_SET) == -1)
+ err(EXIT_FAILURE, "lseek failed");
+
+ int hash_size = crypt_hash_size("sha256");
+ if (hash_size <= 0)
+ err(EXIT_FAILURE, "crypt_hash_size failed");
+
+ if (crypt_hash_final(hd, (char*)csum, (size_t)hash_size))
+ err(EXIT_FAILURE, "crypt_hash_final failed");
+ if (write_buffer(fd, csum, hash_size) != hash_size)
+ err(EXIT_FAILURE, "write_buffer failed");
+ }
+}
+
+void LUKS2ProtoConverter::set_write_headers_only(bool headers_only) {
+ write_headers_only = headers_only;
+}
+
+void LUKS2ProtoConverter::convert(const LUKS2_both_headers &headers, int fd) {
+ uint64_t primary_seqid, secondary_seqid;
+ int result;
+
+ size_t out_size = headers.primary_header().hdr_size() + headers.secondary_header().hdr_size();
+
+ if (!write_headers_only)
+ out_size += KEYSLOTS_SIZE + DATA_SIZE;
+
+ result = ftruncate(fd, out_size);
+ if (result == -1)
+ err(EXIT_FAILURE, "truncate failed");
+
+ result = lseek(fd, 0, SEEK_SET);
+ if (result == -1)
+ err(EXIT_FAILURE, "lseek failed");
+
+ switch (headers.seqid()) {
+ case EQUAL:
+ primary_seqid = 1;
+ secondary_seqid = 1;
+ break;
+ case PRIMARY_GREATER:
+ primary_seqid = 2;
+ secondary_seqid = 1;
+ break;
+ case SECONDARY_GREATER:
+ primary_seqid = 1;
+ secondary_seqid = 2;
+ break;
+ }
+
+ create_jobj(headers);
+ emit_luks2_binary_header(headers.primary_header(), fd, 0, primary_seqid);
+ emit_luks2_binary_header(headers.secondary_header(), fd, headers.primary_header().hdr_size(), secondary_seqid);
+}
+
+std::string LUKS2ProtoConverter::config_flag_to_string(config_flag flag) {
+ switch (flag) {
+ case CONFIG_FLAG_ALLOW_DISCARDS:
+ return "allow-discards";
+ case CONFIG_FLAG_SAME_CPU_CRYPT:
+ return "same-cpu-crypt";
+ case CONFIG_FLAG_SUBMIT_FROM_CRYPT_CPUS:
+ return "submit-from-crypt-cpus";
+ case CONFIG_FLAG_NO_JOURNAL:
+ return "no-journal";
+ case CONFIG_FLAG_NO_READ_WORKQUEUE:
+ return "no-read-workqueue";
+ case CONFIG_FLAG_NO_WRITE_WORKQUEUE:
+ return "no-write-workqueue";
+ }
+}
+
+std::string LUKS2ProtoConverter::config_requirement_to_string(config_requirement requirement) {
+ switch (requirement) {
+ case CONFIG_REQUIREMENT_OFFLINE_REENCRYPT:
+ return "offline-reencrypt";
+ case CONFIG_REQUIREMENT_ONLINE_REENCRYPT_V2:
+ return "online-reencrypt-v2";
+ }
+}
+
+void LUKS2ProtoConverter::generate_config(const config_description &config_desc, uint64_t json_size, uint64_t keyslots_size) {
+ json_object *jobj_config, *jobj_flags, *jobj_requirements, *jobj_mandatory;
+ jobj_config = json_object_new_object();
+
+ json_object_object_add(jobj_config, "json_size", json_object_new_string(std::to_string(json_size).c_str()));
+ json_object_object_add(jobj_config, "keyslots_size", json_object_new_string(std::to_string(keyslots_size).c_str()));
+
+ if (!config_desc.config_flags().empty()) {
+ jobj_flags = json_object_new_array();
+
+ for (const int flag : config_desc.config_flags()) {
+ json_object_array_add(jobj_flags,
+ json_object_new_string(config_flag_to_string(config_flag(flag)).c_str()));
+ }
+
+ /* Replace or add new flags array */
+ json_object_object_add(jobj_config, "flags", jobj_flags);
+ }
+
+ if (!config_desc.requirements().empty()) {
+ jobj_requirements = json_object_new_object();
+ jobj_mandatory = json_object_new_array();
+
+ for (const int requirement : config_desc.requirements()) {
+ json_object_array_add(jobj_mandatory,
+ json_object_new_string(config_requirement_to_string(config_requirement(requirement)).c_str()));
+ }
+
+ /* Replace or add new requirements array */
+ json_object_object_add(jobj_requirements, "mandatory", jobj_mandatory);
+ json_object_object_add(jobj_config, "requirements", jobj_requirements);
+ }
+
+ json_object_object_add(jobj, "config", jobj_config);
+}
+
+LUKS2ProtoConverter::~LUKS2ProtoConverter() {
+ json_object_put(jobj);
+ if (hd)
+ crypt_hash_destroy(hd);
+}
+} // namespace LUKS2_proto
diff --git a/tests/fuzz/proto_to_luks2_converter.h b/tests/fuzz/proto_to_luks2_converter.h
new file mode 100644
index 0000000..9f926d0
--- /dev/null
+++ b/tests/fuzz/proto_to_luks2_converter.h
@@ -0,0 +1,91 @@
+/*
+ * cryptsetup LUKS2 custom mutator fuzz target
+ *
+ * Copyright (C) 2022-2023 Daniel Zatovic <daniel.zatovic@gmail.com>
+ * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef LUKS2_PROTO_CONVERTER_H_
+#define LUKS2_PROTO_CONVERTER_H_
+
+#include <sstream>
+#include <string>
+#include <json-c/json.h>
+
+#include "LUKS2.pb.h"
+extern "C" {
+#include "crypto_backend/crypto_backend.h"
+}
+
+namespace LUKS2_proto {
+
+class LUKS2ProtoConverter {
+ public:
+ ~LUKS2ProtoConverter();
+ std::string string_uint64_to_string(const string_uint64 &str_u64);
+ std::string hash_algorithm_to_string(const hash_algorithm type);
+ std::string object_id_to_string(const object_id &oid);
+
+ std::string keyslot_area_type_to_string(const keyslot_area_type type);
+ std::string keyslot_kdf_type_to_string(const keyslot_kdf_type type);
+ std::string reencrypt_keyslot_mode_to_string(const reencrypt_keyslot_mode mode);
+ std::string keyslot_type_to_string(const keyslot_type type);
+ std::string reencrypt_keyslot_direction_to_string(const reencrypt_keyslot_direction direction);
+ std::string keyslot_af_type_to_string(const keyslot_af_type type);
+
+ std::string config_flag_to_string(config_flag flag);
+ std::string config_requirement_to_string(config_requirement requirements);
+
+ std::string segment_type_to_string(segment_type type);
+ std::string segment_flag_to_string(segment_flag flag);
+
+ void generate_keyslot(struct json_object *jobj_keyslots, const keyslot_description &keyslot_desc);
+ void generate_keyslot_area(struct json_object *jobj_area, const keyslot_area_description &keyslot_area_desc);
+ void generate_keyslot_kdf(struct json_object *jobj_kdf, const keyslot_kdf_description &keyslot_kdf_desc);
+ void generate_keyslot_af(struct json_object *jobj_af, const keyslot_af_description &keyslot_af_desc);
+
+ void generate_token(struct json_object *jobj_tokens, const token_description &token_desc);
+
+ void generate_digest(struct json_object *jobj_digests, const digest_description &digest_desc);
+
+ void generate_segment_integrity(struct json_object *jobj_integrity, const segment_integrity_description &segment_integrity_desc);
+ void generate_segment(struct json_object *jobj_segments, const segment_description &segment_desc);
+
+ void generate_config(const config_description &config_desc, uint64_t json_size, uint64_t keyslots_size);
+
+ void create_jobj(const LUKS2_both_headers &headers, uint64_t hdr_size);
+ void emit_luks2_binary_header(uint64_t offset, uint64_t seqid, bool is_primary, uint64_t hdr_size);
+ void convert(const LUKS2_both_headers &headers, int fd);
+ void create_jobj(const LUKS2_both_headers &headers);
+ void emit_luks2_binary_header(const LUKS2_header &header_proto, int fd, uint64_t offset, uint64_t seqid);
+
+ void set_write_headers_only(bool headers_only);
+
+ const uint8_t *get_out_buffer();
+ size_t get_out_size();
+
+ static const uint64_t KEYSLOTS_SIZE = 3 * 1024 * 1024;
+ static const uint64_t DATA_SIZE = 16 * 1024 * 1024;
+ private:
+ bool write_headers_only = false;
+ struct crypt_hash *hd = NULL;
+ struct ::json_object *jobj = NULL;
+};
+
+} // namespace LUKS2_proto
+
+#endif // LUKS2_PROTO_CONVERTER_H_
diff --git a/tests/fuzz/unpoison-mutated-buffers-from-libfuzzer.patch b/tests/fuzz/unpoison-mutated-buffers-from-libfuzzer.patch
new file mode 100644
index 0000000..1f48339
--- /dev/null
+++ b/tests/fuzz/unpoison-mutated-buffers-from-libfuzzer.patch
@@ -0,0 +1,29 @@
+diff --git a/src/libfuzzer/libfuzzer_mutator.cc b/src/libfuzzer/libfuzzer_mutator.cc
+index 34d144c..b671fd4 100644
+--- a/src/libfuzzer/libfuzzer_mutator.cc
++++ b/src/libfuzzer/libfuzzer_mutator.cc
+@@ -14,6 +14,8 @@
+
+ #include "src/libfuzzer/libfuzzer_mutator.h"
+
++#include <sanitizer/msan_interface.h>
++
+ #include <string.h>
+
+ #include <algorithm>
+@@ -64,6 +66,7 @@ template <class T>
+ T MutateValue(T v) {
+ size_t size =
+ LLVMFuzzerMutate(reinterpret_cast<uint8_t*>(&v), sizeof(v), sizeof(v));
++ __msan_unpoison(reinterpret_cast<uint8_t*>(&v), size);
+ memset(reinterpret_cast<uint8_t*>(&v) + size, 0, sizeof(v) - size);
+ return v;
+ }
+@@ -93,6 +96,7 @@ std::string Mutator::MutateString(const std::string& value,
+ result.resize(std::max(1, new_size));
+ result.resize(LLVMFuzzerMutate(reinterpret_cast<uint8_t*>(&result[0]),
+ value.size(), result.size()));
++ __msan_unpoison(reinterpret_cast<uint8_t*>(&result[0]), result.size());
+ return result;
+ }
+