diff options
Diffstat (limited to 'tests/fuzz')
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; + } + |