/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * 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.
 */

//
// Test that file contents encryption is working, via:
//
// - Correctness tests.  These test the standard FBE settings supported by
//   Android R and higher.
//
// - Randomness test.  This runs on all devices that use FBE, even old ones.
//
// The correctness tests cover the following settings:
//
//    fileencryption=aes-256-xts:aes-256-cts:v2
//    fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized
//    fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized+wrappedkey_v0
//    fileencryption=aes-256-xts:aes-256-cts:v2+emmc_optimized
//    fileencryption=aes-256-xts:aes-256-cts:v2+emmc_optimized+wrappedkey_v0
//    fileencryption=adiantum:adiantum:v2
//
// On devices launching with R or higher those are equivalent to simply:
//
//    fileencryption=
//    fileencryption=::inlinecrypt_optimized
//    fileencryption=::inlinecrypt_optimized+wrappedkey_v0
//    fileencryption=::emmc_optimized
//    fileencryption=::emmc_optimized+wrappedkey_v0
//    fileencryption=adiantum
//
// The tests don't check which one of those settings, if any, the device is
// actually using; they just try to test everything they can.
// "fileencryption=aes-256-xts" is guaranteed to be available if the kernel
// supports any "fscrypt v2" features at all.  The others may not be available,
// so the tests take that into account and skip testing them when unavailable.
//
// None of these tests should ever fail.  In particular, vendors must not break
// any standard FBE settings, regardless of what the device actually uses.  If
// any test fails, make sure to check things like the byte order of keys.
//

#include <android-base/file.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <asm/byteorder.h>
#include <errno.h>
#include <fcntl.h>
#include <gtest/gtest.h>
#include <limits.h>
#include <linux/f2fs.h>
#include <linux/fiemap.h>
#include <linux/fs.h>
#include <linux/fscrypt.h>
#include <lz4.h>
#include <openssl/evp.h>
#include <openssl/hkdf.h>
#include <openssl/siphash.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

#include <chrono>
#include <thread>

#include "vts_kernel_encryption.h"

/* These values are missing from <linux/f2fs.h> */
enum f2fs_compress_algorithm {
  F2FS_COMPRESS_LZO,
  F2FS_COMPRESS_LZ4,
  F2FS_COMPRESS_ZSTD,
  F2FS_COMPRESS_LZORLE,
  F2FS_COMPRESS_MAX,
};

namespace android {
namespace kernel {

// The main mountpoint of the filesystem the test will use to test FBE.
constexpr const char *kTestMountpoint = "/data";

// A directory on the kTestMountpoint filesystem that doesn't already have an
// encryption policy, and therefore allows the creation of subdirectories with
// custom encryption policies.
constexpr const char *kUnencryptedDir = "/data/unencrypted";

// A directory on the kTestMountpoint filesystem that already has an encryption
// policy.  Any files created in this directory will be encrypted using the
// encryption settings that Android is configured to use.
constexpr const char *kTmpDir = "/data/local/tmp";

// Assumed size of filesystem blocks, in bytes
constexpr int kFilesystemBlockSize = 4096;

// Size of the test file in filesystem blocks
constexpr int kTestFileBlocks = 256;

// Size of the test file in bytes
constexpr int kTestFileBytes = kFilesystemBlockSize * kTestFileBlocks;

// fscrypt master key size in bytes
constexpr int kFscryptMasterKeySize = 64;

// fscrypt maximum IV size in bytes
constexpr int kFscryptMaxIVSize = 32;

// fscrypt per-file nonce size in bytes
constexpr int kFscryptFileNonceSize = 16;

// fscrypt HKDF context bytes, from kernel fs/crypto/fscrypt_private.h
enum FscryptHkdfContext {
  HKDF_CONTEXT_KEY_IDENTIFIER = 1,
  HKDF_CONTEXT_PER_FILE_ENC_KEY = 2,
  HKDF_CONTEXT_DIRECT_KEY = 3,
  HKDF_CONTEXT_IV_INO_LBLK_64_KEY = 4,
  HKDF_CONTEXT_DIRHASH_KEY = 5,
  HKDF_CONTEXT_IV_INO_LBLK_32_KEY = 6,
  HKDF_CONTEXT_INODE_HASH_KEY = 7,
};

struct FscryptFileNonce {
  uint8_t bytes[kFscryptFileNonceSize];
};

// Format of the initialization vector
union FscryptIV {
  struct {
    __le32 lblk_num;      // file logical block number, starts at 0
    __le32 inode_number;  // only used for IV_INO_LBLK_64
    uint8_t file_nonce[kFscryptFileNonceSize];  // only used for DIRECT_KEY
  };
  uint8_t bytes[kFscryptMaxIVSize];
};

struct TestFileInfo {
  std::vector<uint8_t> plaintext;
  std::vector<uint8_t> actual_ciphertext;
  uint64_t inode_number;
  FscryptFileNonce nonce;
};

static bool GetInodeNumber(const std::string &path, uint64_t *inode_number) {
  struct stat stbuf;
  if (stat(path.c_str(), &stbuf) != 0) {
    ADD_FAILURE() << "Failed to stat " << path << Errno();
    return false;
  }
  *inode_number = stbuf.st_ino;
  return true;
}

//
// Checks whether the kernel has support for the following fscrypt features:
//
// - Filesystem-level keyring (FS_IOC_ADD_ENCRYPTION_KEY and
//   FS_IOC_REMOVE_ENCRYPTION_KEY)
// - v2 encryption policies
// - The IV_INO_LBLK_64 encryption policy flag
// - The FS_IOC_GET_ENCRYPTION_NONCE ioctl
// - The IV_INO_LBLK_32 encryption policy flag
//
// To do this it's sufficient to just check whether FS_IOC_ADD_ENCRYPTION_KEY is
// available, as the other features were added in the same AOSP release.
//
// The easiest way to do this is to just execute the ioctl with a NULL argument.
// If available it will fail with EFAULT; otherwise it will fail with ENOTTY (or
// EOPNOTSUPP if encryption isn't enabled on the filesystem; that happens on old
// devices that aren't using FBE and are upgraded to a new kernel).
//
static bool IsFscryptV2Supported(const std::string &mountpoint) {
  android::base::unique_fd fd(
      open(mountpoint.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC));
  if (fd < 0) {
    ADD_FAILURE() << "Failed to open " << mountpoint << Errno();
    return false;
  }

  if (ioctl(fd, FS_IOC_ADD_ENCRYPTION_KEY, nullptr) == 0) {
    ADD_FAILURE()
        << "FS_IOC_ADD_ENCRYPTION_KEY(nullptr) unexpectedly succeeded on "
        << mountpoint;
    return false;
  }
  switch (errno) {
    case EFAULT:
      return true;
    case EOPNOTSUPP:
    case ENOTTY:
      GTEST_LOG_(INFO) << "No support for FS_IOC_ADD_ENCRYPTION_KEY on "
                       << mountpoint;
      return false;
    default:
      ADD_FAILURE()
          << "Unexpected error from FS_IOC_ADD_ENCRYPTION_KEY(nullptr) on "
          << mountpoint << Errno();
      return false;
  }
}

// Helper class to freeze / unfreeze a filesystem, to prevent the filesystem
// from moving the file's blocks while the test is accessing them via the
// underlying device.  ext4 doesn't need this, but f2fs does because f2fs does
// background garbage collection.  We cannot use F2FS_IOC_SET_PIN_FILE because
// F2FS_IOC_SET_PIN_FILE doesn't support compressed files.
//
// The fd given can be any fd to a file or directory on the filesystem.
// FIFREEZE operates on the whole filesystem, not on the individual file given.
class ScopedFsFreezer {
 public:
  explicit ScopedFsFreezer(int fd) {
    auto start = std::chrono::steady_clock::now();
    do {
      if (ioctl(fd, FIFREEZE, NULL) == 0) {
        fd_ = fd;
        return;
      }
      if (errno == EBUSY || errno == EINVAL) {
        // EBUSY means the filesystem is already frozen, perhaps by a concurrent
        // execution of this same test.  Since we don't have control over
        // exactly when another process unfreezes the filesystem, we don't
        // continue on with the test but rather just keep retrying the freeze
        // until it works.
        //
        // Very rarely, on f2fs FIFREEZE fails with EINVAL (b/255800104).
        // Unfortunately, the reason for this is still unknown.  Enter the retry
        // loop in this case too, in the hope that it helps.
        //
        // Both of these errors are rare, so this sleep should not normally be
        // executed.
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        continue;
      }
      ADD_FAILURE() << "Failed to freeze filesystem" << Errno();
      return;
    } while (std::chrono::steady_clock::now() - start <
             std::chrono::seconds(20));
    ADD_FAILURE() << "Timed out while waiting to freeze filesystem";
  }

  ~ScopedFsFreezer() {
    if (fd_ != -1 && ioctl(fd_, FITHAW, NULL) != 0) {
      ADD_FAILURE() << "Failed to thaw filesystem" << Errno();
    }
  }

 private:
  int fd_ = -1;
};

// Reads the raw data of a file specified by |fd|. The file is located on the
// filesystem specified by |fs_info|. The file has |expected_data_size| bytes of
// initialized data; this must be a multiple of the filesystem block size
// kFilesystemBlockSize.  The file may contain holes, in which case only the
// non-holes are read; the holes are not counted in |expected_data_size|.
static bool ReadRawDataOfFile(int fd, const FilesystemInfo &fs_info,
                              int expected_data_size,
                              std::vector<uint8_t> *raw_data) {
  int max_extents = expected_data_size / kFilesystemBlockSize;

  EXPECT_TRUE(expected_data_size % kFilesystemBlockSize == 0);

  if (fsync(fd) != 0) {
    ADD_FAILURE() << "Failed to sync file" << Errno();
    return false;
  }

  // Freeze the filesystem containing the file.
  ScopedFsFreezer freezer(fd);

  // Query the file's extents.
  size_t allocsize = offsetof(struct fiemap, fm_extents[max_extents]);
  std::unique_ptr<struct fiemap> map(
      new (::operator new(allocsize)) struct fiemap);
  memset(map.get(), 0, allocsize);
  map->fm_flags = 0;
  map->fm_length = UINT64_MAX;
  map->fm_extent_count = max_extents;
  if (ioctl(fd, FS_IOC_FIEMAP, map.get()) != 0) {
    ADD_FAILURE() << "Failed to get extents of file" << Errno();
    return false;
  }

  // Read the raw data, using direct I/O to avoid getting any stale cached data.
  // Direct I/O requires using a block size aligned buffer.

  std::unique_ptr<void, void (*)(void *)> buf_mem(
      aligned_alloc(kFilesystemBlockSize, expected_data_size), free);
  if (buf_mem == nullptr) {
    ADD_FAILURE() << "Out of memory";
    return false;
  }
  uint8_t *buf = static_cast<uint8_t *>(buf_mem.get());
  int offset = 0;

  for (int i = 0; i < map->fm_mapped_extents; i++) {
    struct fiemap_extent &extent = map->fm_extents[i];

    GTEST_LOG_(INFO) << "Extent " << i + 1 << " of " << map->fm_mapped_extents
                     << " is logical offset " << extent.fe_logical
                     << ", physical offset " << extent.fe_physical
                     << ", length " << extent.fe_length << ", flags 0x"
                     << std::hex << extent.fe_flags << std::dec;
    // Make sure the flags indicate that fe_physical is actually valid.
    if (extent.fe_flags & (FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_UNWRITTEN)) {
      ADD_FAILURE() << "Unsupported extent flags: 0x" << std::hex
                    << extent.fe_flags << std::dec;
      return false;
    }
    if (extent.fe_length % kFilesystemBlockSize != 0) {
      ADD_FAILURE()
          << "Extent (length) is not aligned to filesystem block size";
      return false;
    }
    if (extent.fe_physical % kFilesystemBlockSize != 0) {
      ADD_FAILURE() << "Extent (physical address) is not aligned to filesystem "
                       "block size";
      return false;
    }
    if (extent.fe_length > expected_data_size - offset) {
      ADD_FAILURE() << "File is longer than expected";
      return false;
    }
    // Find the raw block device and remap the physical offset.
    std::string raw_blk_device;
    for (const DiskMapEntry &map_entry : fs_info.disk_map) {
      if (extent.fe_physical / kFilesystemBlockSize <= map_entry.end_blkaddr) {
        if ((extent.fe_physical + extent.fe_length) / kFilesystemBlockSize >
            (map_entry.end_blkaddr + 1)) {
          ADD_FAILURE() << "Extent spans multiple block devices";
          return false;
        }
        raw_blk_device = map_entry.raw_blk_device;
        extent.fe_physical -= map_entry.start_blkaddr * kFilesystemBlockSize;
        break;
      }
    }
    if (raw_blk_device.empty()) {
      ADD_FAILURE()
          << "Failed to find a raw block device in the block device list";
      return false;
    }
    // Open the raw block device and read out the data.
    android::base::unique_fd blk_fd(
        open(raw_blk_device.c_str(), O_RDONLY | O_DIRECT | O_CLOEXEC));
    if (blk_fd < 0) {
      ADD_FAILURE() << "Failed to open raw block device " << raw_blk_device
                    << Errno();
      return false;
    }
    if (pread(blk_fd, &buf[offset], extent.fe_length, extent.fe_physical) !=
        extent.fe_length) {
      ADD_FAILURE() << "Error reading raw data from block device" << Errno();
      return false;
    }
    offset += extent.fe_length;
  }
  if (offset != expected_data_size) {
    ADD_FAILURE() << "File is shorter than expected";
    return false;
  }
  *raw_data = std::vector<uint8_t>(&buf[0], &buf[offset]);
  return true;
}

// Writes |plaintext| to a file |path| on the filesystem |fs_info|.
// Returns in |ciphertext| the file's raw ciphertext read from disk.
static bool WriteTestFile(const std::vector<uint8_t> &plaintext,
                          const std::string &path,
                          const FilesystemInfo &fs_info,
                          const struct f2fs_comp_option *compress_options,
                          std::vector<uint8_t> *ciphertext) {
  GTEST_LOG_(INFO) << "Creating test file " << path << " containing "
                   << plaintext.size() << " bytes of data";
  android::base::unique_fd fd(
      open(path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600));
  if (fd < 0) {
    ADD_FAILURE() << "Failed to create " << path << Errno();
    return false;
  }

  if (compress_options != nullptr) {
    if (ioctl(fd, F2FS_IOC_SET_COMPRESS_OPTION, compress_options) != 0) {
      ADD_FAILURE() << "Error setting compression options on " << path
                    << Errno();
      return false;
    }
  }

  if (!android::base::WriteFully(fd, plaintext.data(), plaintext.size())) {
    ADD_FAILURE() << "Error writing to " << path << Errno();
    return false;
  }

  if (compress_options != nullptr) {
    // With compress_mode=user, files in a compressed directory inherit the
    // compression flag but aren't actually compressed unless
    // F2FS_IOC_COMPRESS_FILE is called.  The ioctl compresses existing data
    // only, so it must be called *after* writing the data.  With
    // compress_mode=fs, the ioctl is unnecessary and fails with EOPNOTSUPP.
    if (ioctl(fd, F2FS_IOC_COMPRESS_FILE, NULL) != 0 && errno != EOPNOTSUPP) {
      ADD_FAILURE() << "F2FS_IOC_COMPRESS_FILE failed on " << path << Errno();
      return false;
    }
  }

  GTEST_LOG_(INFO) << "Reading the raw ciphertext of " << path << " from disk";
  if (!ReadRawDataOfFile(fd, fs_info, plaintext.size(), ciphertext)) {
    ADD_FAILURE() << "Failed to read the raw ciphertext of " << path;
    return false;
  }
  return true;
}

// See MakeSomeCompressibleClusters() for explanation.
static bool IsCompressibleCluster(int cluster_num) {
  return cluster_num % 2 == 0;
}

// Given some random data that will be written to the test file, modifies every
// other compression cluster to be compressible by at least 1 filesystem block.
//
// This testing strategy is adapted from the xfstest "f2fs/002".  We use some
// compressible clusters and some incompressible clusters because we want to
// test that the encryption works correctly with both.  We also don't make the
// data *too* compressible, since we want to have enough compressed blocks in
// each cluster to see the IVs being incremented.
static bool MakeSomeCompressibleClusters(std::vector<uint8_t> &bytes,
                                         int log_cluster_size) {
  int cluster_bytes = kFilesystemBlockSize << log_cluster_size;
  if (bytes.size() % cluster_bytes != 0) {
    ADD_FAILURE() << "Test file size (" << bytes.size()
                  << " bytes) is not divisible by compression cluster size ("
                  << cluster_bytes << " bytes)";
    return false;
  }
  int num_clusters = bytes.size() / cluster_bytes;
  for (int i = 0; i < num_clusters; i++) {
    if (IsCompressibleCluster(i)) {
      memset(&bytes[i * cluster_bytes], 0, 2 * kFilesystemBlockSize);
    }
  }
  return true;
}

// On-disk format of an f2fs compressed cluster
struct f2fs_compressed_cluster {
  __le32 clen;
  __le32 reserved[5];
  uint8_t cdata[];
} __attribute__((packed));

static bool DecompressLZ4Cluster(const uint8_t *in, uint8_t *out,
                                 int cluster_bytes) {
  const struct f2fs_compressed_cluster *cluster =
      reinterpret_cast<const struct f2fs_compressed_cluster *>(in);
  uint32_t clen = __le32_to_cpu(cluster->clen);

  if (clen > cluster_bytes - kFilesystemBlockSize - sizeof(*cluster)) {
    ADD_FAILURE() << "Invalid compressed cluster (bad compressed size)";
    return false;
  }
  if (LZ4_decompress_safe(reinterpret_cast<const char *>(cluster->cdata),
                          reinterpret_cast<char *>(out), clen,
                          cluster_bytes) != cluster_bytes) {
    ADD_FAILURE() << "Invalid compressed cluster (LZ4 decompression error)";
    return false;
  }

  // As long as we're here, do a regression test for kernel commit 7fa6d59816e7
  // ("f2fs: fix leaking uninitialized memory in compressed clusters").
  // Note that if this fails, we can still continue with the rest of the test.
  size_t full_clen = offsetof(struct f2fs_compressed_cluster, cdata[clen]);
  if (full_clen % kFilesystemBlockSize != 0) {
    size_t remainder =
        kFilesystemBlockSize - (full_clen % kFilesystemBlockSize);
    std::vector<uint8_t> zeroes(remainder, 0);
    std::vector<uint8_t> actual(&cluster->cdata[clen],
                                &cluster->cdata[clen + remainder]);
    EXPECT_EQ(zeroes, actual);
  }
  return true;
}

class FBEPolicyTest : public ::testing::Test {
 protected:
  void SetUp() override;
  void TearDown() override;
  bool SetMasterKey(const std::vector<uint8_t> &master_key, uint32_t flags = 0,
                    bool required = true);
  bool CreateAndSetHwWrappedKey(std::vector<uint8_t> *enc_key,
                                std::vector<uint8_t> *sw_secret);
  int GetSkipFlagsForInoBasedEncryption();
  bool SetEncryptionPolicy(int contents_mode, int filenames_mode, int flags,
                           int skip_flags);
  bool GenerateTestFile(
      TestFileInfo *info,
      const struct f2fs_comp_option *compress_options = nullptr);
  bool VerifyKeyIdentifier(const std::vector<uint8_t> &master_key);
  bool DerivePerModeEncryptionKey(const std::vector<uint8_t> &master_key,
                                  int mode, FscryptHkdfContext context,
                                  std::vector<uint8_t> &enc_key);
  bool DerivePerFileEncryptionKey(const std::vector<uint8_t> &master_key,
                                  const FscryptFileNonce &nonce,
                                  std::vector<uint8_t> &enc_key);
  void VerifyCiphertext(const std::vector<uint8_t> &enc_key,
                        const FscryptIV &starting_iv, const Cipher &cipher,
                        const TestFileInfo &file_info);
  void TestEmmcOptimizedDunWraparound(const std::vector<uint8_t> &master_key,
                                      const std::vector<uint8_t> &enc_key);
  bool EnableF2fsCompressionOnTestDir();
  bool F2fsCompressOptionsSupported(const struct f2fs_comp_option &opts);
  std::string test_dir_;
  std::string test_file_;
  struct fscrypt_key_specifier master_key_specifier_;
  bool skip_test_ = false;
  bool key_added_ = false;
  FilesystemInfo fs_info_;
};

// Test setup procedure.  Creates a test directory test_dir_ and does other
// preparations. skip_test_ is set to true if the test should be skipped.
void FBEPolicyTest::SetUp() {
  if (!IsFscryptV2Supported(kTestMountpoint)) {
    int first_api_level;
    ASSERT_TRUE(GetFirstApiLevel(&first_api_level));
    // Devices launching with R or higher must support fscrypt v2.
    ASSERT_LE(first_api_level, __ANDROID_API_Q__);
    GTEST_LOG_(INFO) << "Skipping test because fscrypt v2 is unsupported";
    skip_test_ = true;
    return;
  }

  // Make sure that if multiple test processes run simultaneously, they generate
  // different encryption keys.
  srand(getpid());

  test_dir_ = android::base::StringPrintf("%s/FBEPolicyTest.%d",
                                          kUnencryptedDir, getpid());
  test_file_ = test_dir_ + "/file";

  ASSERT_TRUE(GetFilesystemInfo(kTestMountpoint, &fs_info_));

  DeleteRecursively(test_dir_);
  if (mkdir(test_dir_.c_str(), 0700) != 0) {
    FAIL() << "Failed to create " << test_dir_ << Errno();
  }
}

void FBEPolicyTest::TearDown() {
  DeleteRecursively(test_dir_);

  // Remove the test key from kTestMountpoint.
  if (key_added_) {
    android::base::unique_fd mntfd(
        open(kTestMountpoint, O_RDONLY | O_DIRECTORY | O_CLOEXEC));
    if (mntfd < 0) {
      FAIL() << "Failed to open " << kTestMountpoint << Errno();
    }
    struct fscrypt_remove_key_arg arg;
    memset(&arg, 0, sizeof(arg));
    arg.key_spec = master_key_specifier_;

    if (ioctl(mntfd, FS_IOC_REMOVE_ENCRYPTION_KEY, &arg) != 0) {
      FAIL() << "FS_IOC_REMOVE_ENCRYPTION_KEY failed on " << kTestMountpoint
             << Errno();
    }
  }
}

// Adds |master_key| to kTestMountpoint and places the resulting key identifier
// in master_key_specifier_.
bool FBEPolicyTest::SetMasterKey(const std::vector<uint8_t> &master_key,
                                 uint32_t flags, bool required) {
  size_t allocsize = sizeof(struct fscrypt_add_key_arg) + master_key.size();
  std::unique_ptr<struct fscrypt_add_key_arg> arg(
      new (::operator new(allocsize)) struct fscrypt_add_key_arg);
  memset(arg.get(), 0, allocsize);
  arg->key_spec.type = FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER;
  arg->__flags = flags;
  arg->raw_size = master_key.size();
  std::copy(master_key.begin(), master_key.end(), arg->raw);

  GTEST_LOG_(INFO) << "Adding fscrypt master key, flags are 0x" << std::hex
                   << flags << std::dec << ", raw bytes are "
                   << BytesToHex(master_key);
  android::base::unique_fd mntfd(
      open(kTestMountpoint, O_RDONLY | O_DIRECTORY | O_CLOEXEC));
  if (mntfd < 0) {
    ADD_FAILURE() << "Failed to open " << kTestMountpoint << Errno();
    return false;
  }
  if (ioctl(mntfd, FS_IOC_ADD_ENCRYPTION_KEY, arg.get()) != 0) {
    if (required || (errno != EINVAL && errno != EOPNOTSUPP)) {
      ADD_FAILURE() << "FS_IOC_ADD_ENCRYPTION_KEY failed on " << kTestMountpoint
                    << Errno();
    }
    return false;
  }
  master_key_specifier_ = arg->key_spec;
  GTEST_LOG_(INFO) << "Master key identifier is "
                   << BytesToHex(master_key_specifier_.u.identifier);
  key_added_ = true;
  if (!(flags & __FSCRYPT_ADD_KEY_FLAG_HW_WRAPPED) &&
      !VerifyKeyIdentifier(master_key))
    return false;
  return true;
}

// Creates a hardware-wrapped key, adds it to the filesystem, and derives the
// corresponding inline encryption key |enc_key| and software secret
// |sw_secret|.  Returns false if unsuccessful (either the test failed, or the
// device doesn't support hardware-wrapped keys so the test should be skipped).
bool FBEPolicyTest::CreateAndSetHwWrappedKey(std::vector<uint8_t> *enc_key,
                                             std::vector<uint8_t> *sw_secret) {
  std::vector<uint8_t> master_key, exported_key;
  if (!CreateHwWrappedKey(&master_key, &exported_key)) return false;

  if (!SetMasterKey(exported_key, __FSCRYPT_ADD_KEY_FLAG_HW_WRAPPED, false)) {
    if (!HasFailure()) {
      GTEST_LOG_(INFO) << "Skipping test because kernel doesn't support "
                          "hardware-wrapped keys";
    }
    return false;
  }

  if (!DeriveHwWrappedEncryptionKey(master_key, enc_key)) return false;
  if (!DeriveHwWrappedRawSecret(master_key, sw_secret)) return false;

  if (!VerifyKeyIdentifier(*sw_secret)) return false;

  return true;
}

enum {
  kSkipIfNoPolicySupport = 1 << 0,
  kSkipIfNoCryptoAPISupport = 1 << 1,
  kSkipIfInlineEncryptionNotUsable = 1 << 2,
};

// Returns 0 if encryption policies that include the inode number in the IVs
// (e.g. IV_INO_LBLK_64) are guaranteed to be settable on the test filesystem.
// Else returns kSkipIfNoPolicySupport.
//
// On f2fs, they're always settable.  On ext4, they're only settable if the
// filesystem has the 'stable_inodes' feature flag.  Android only sets
// 'stable_inodes' if the device uses one of these encryption policies "for
// real", e.g. "fileencryption=::inlinecrypt_optimized" in fstab.  Since the
// fstab could contain something else, we have to allow the tests for these
// encryption policies to be skipped on ext4.
int FBEPolicyTest::GetSkipFlagsForInoBasedEncryption() {
  if (fs_info_.type == "ext4") return kSkipIfNoPolicySupport;
  return 0;
}

// Sets a v2 encryption policy on the test directory.  The policy will use the
// test key and the specified encryption modes and flags.  If the kernel doesn't
// support setting or using the encryption policy, then a failure will be added,
// unless the reason is covered by a bit set in |skip_flags|.
bool FBEPolicyTest::SetEncryptionPolicy(int contents_mode, int filenames_mode,
                                        int flags, int skip_flags) {
  if (!key_added_) {
    ADD_FAILURE() << "SetEncryptionPolicy called but no key added";
    return false;
  }

  struct fscrypt_policy_v2 policy;
  memset(&policy, 0, sizeof(policy));
  policy.version = FSCRYPT_POLICY_V2;
  policy.contents_encryption_mode = contents_mode;
  policy.filenames_encryption_mode = filenames_mode;
  // Always give PAD_16, to match the policies that Android sets for real.
  // It doesn't affect contents encryption, though.
  policy.flags = flags | FSCRYPT_POLICY_FLAGS_PAD_16;
  memcpy(policy.master_key_identifier, master_key_specifier_.u.identifier,
         FSCRYPT_KEY_IDENTIFIER_SIZE);

  android::base::unique_fd dirfd(
      open(test_dir_.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC));
  if (dirfd < 0) {
    ADD_FAILURE() << "Failed to open " << test_dir_ << Errno();
    return false;
  }
  GTEST_LOG_(INFO) << "Setting encryption policy on " << test_dir_;
  if (ioctl(dirfd, FS_IOC_SET_ENCRYPTION_POLICY, &policy) != 0) {
    if (errno == EINVAL && (skip_flags & kSkipIfNoPolicySupport)) {
      GTEST_LOG_(INFO) << "Skipping test because encryption policy is "
                          "unsupported on this filesystem / kernel";
      return false;
    }
    ADD_FAILURE() << "FS_IOC_SET_ENCRYPTION_POLICY failed on " << test_dir_
                  << " using contents_mode=" << contents_mode
                  << ", filenames_mode=" << filenames_mode << ", flags=0x"
                  << std::hex << flags << std::dec << Errno();
    return false;
  }
  if (skip_flags &
      (kSkipIfNoCryptoAPISupport | kSkipIfInlineEncryptionNotUsable)) {
    android::base::unique_fd fd(
        open(test_file_.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600));
    if (fd < 0) {
      // Setting an encryption policy that uses modes that aren't enabled in the
      // kernel's crypto API (e.g. FSCRYPT_MODE_ADIANTUM when the kernel lacks
      // CONFIG_CRYPTO_ADIANTUM) will still succeed, but actually creating a
      // file will fail with ENOPKG.  Make sure to check for this case.
      if (errno == ENOPKG && (skip_flags & kSkipIfNoCryptoAPISupport)) {
        GTEST_LOG_(INFO)
            << "Skipping test because encryption policy is "
               "unsupported on this kernel, due to missing crypto API support";
        return false;
      }
      // We get EINVAL here when we're using a hardware-wrapped key, the device
      // has inline encryption hardware that supports hardware-wrapped keys, and
      // there are hardware or kernel limitations that make it impossible for
      // inline encryption to actually be used with the policy.  For example:
      //
      //   - The device's inline encryption hardware doesn't support the number
      //     of DUN bytes needed for file contents encryption.
      //
      //   - The policy uses the IV_INO_LBLK_32 flag, and the filesystem block
      //     size differs from the page size.  (Kernel limitation.)
      if (errno == EINVAL && (skip_flags & kSkipIfInlineEncryptionNotUsable)) {
        GTEST_LOG_(INFO)
            << "Skipping test because encryption policy requires inline "
               "encryption, but inline encryption is unsupported with this "
               "policy on this device due to hardware or kernel limitations";
        return false;
      }
    }
    unlink(test_file_.c_str());
  }
  return true;
}

// Generates some test data, writes it to a file in the test directory, and
// returns in |info| the file's plaintext, the file's raw ciphertext read from
// disk, and other information about the file.
bool FBEPolicyTest::GenerateTestFile(
    TestFileInfo *info, const struct f2fs_comp_option *compress_options) {
  info->plaintext.resize(kTestFileBytes);
  RandomBytesForTesting(info->plaintext);

  if (compress_options != nullptr &&
      !MakeSomeCompressibleClusters(info->plaintext,
                                    compress_options->log_cluster_size))
    return false;

  if (!WriteTestFile(info->plaintext, test_file_, fs_info_, compress_options,
                     &info->actual_ciphertext))
    return false;

  android::base::unique_fd fd(open(test_file_.c_str(), O_RDONLY | O_CLOEXEC));
  if (fd < 0) {
    ADD_FAILURE() << "Failed to open " << test_file_ << Errno();
    return false;
  }

  // Get the file's inode number.
  if (!GetInodeNumber(test_file_, &info->inode_number)) return false;
  GTEST_LOG_(INFO) << "Inode number: " << info->inode_number;

  // Get the file's nonce.
  if (ioctl(fd, FS_IOC_GET_ENCRYPTION_NONCE, info->nonce.bytes) != 0) {
    ADD_FAILURE() << "FS_IOC_GET_ENCRYPTION_NONCE failed on " << test_file_
                  << Errno();
    return false;
  }
  GTEST_LOG_(INFO) << "File nonce: " << BytesToHex(info->nonce.bytes);
  return true;
}

static std::vector<uint8_t> InitHkdfInfo(FscryptHkdfContext context) {
  return {
      'f', 's', 'c', 'r', 'y', 'p', 't', '\0', static_cast<uint8_t>(context)};
}

static bool DeriveKey(const std::vector<uint8_t> &master_key,
                      const std::vector<uint8_t> &hkdf_info,
                      std::vector<uint8_t> &out) {
  if (HKDF(out.data(), out.size(), EVP_sha512(), master_key.data(),
           master_key.size(), nullptr, 0, hkdf_info.data(),
           hkdf_info.size()) != 1) {
    ADD_FAILURE() << "BoringSSL HKDF-SHA512 call failed";
    return false;
  }
  GTEST_LOG_(INFO) << "Derived subkey " << BytesToHex(out)
                   << " using HKDF info " << BytesToHex(hkdf_info);
  return true;
}

// Derives the key identifier from |master_key| and verifies that it matches the
// value the kernel returned in |master_key_specifier_|.
bool FBEPolicyTest::VerifyKeyIdentifier(
    const std::vector<uint8_t> &master_key) {
  std::vector<uint8_t> hkdf_info = InitHkdfInfo(HKDF_CONTEXT_KEY_IDENTIFIER);
  std::vector<uint8_t> computed_key_identifier(FSCRYPT_KEY_IDENTIFIER_SIZE);
  if (!DeriveKey(master_key, hkdf_info, computed_key_identifier)) return false;

  std::vector<uint8_t> actual_key_identifier(
      std::begin(master_key_specifier_.u.identifier),
      std::end(master_key_specifier_.u.identifier));
  EXPECT_EQ(actual_key_identifier, computed_key_identifier);
  return actual_key_identifier == computed_key_identifier;
}

// Derives a per-mode encryption key from |master_key|, |mode|, |context|, and
// (if needed for the context) the filesystem UUID.
bool FBEPolicyTest::DerivePerModeEncryptionKey(
    const std::vector<uint8_t> &master_key, int mode,
    FscryptHkdfContext context, std::vector<uint8_t> &enc_key) {
  std::vector<uint8_t> hkdf_info = InitHkdfInfo(context);

  hkdf_info.push_back(mode);
  if (context == HKDF_CONTEXT_IV_INO_LBLK_64_KEY ||
      context == HKDF_CONTEXT_IV_INO_LBLK_32_KEY)
    hkdf_info.insert(hkdf_info.end(), fs_info_.uuid.bytes,
                     std::end(fs_info_.uuid.bytes));

  return DeriveKey(master_key, hkdf_info, enc_key);
}

// Derives a per-file encryption key from |master_key| and |nonce|.
bool FBEPolicyTest::DerivePerFileEncryptionKey(
    const std::vector<uint8_t> &master_key, const FscryptFileNonce &nonce,
    std::vector<uint8_t> &enc_key) {
  std::vector<uint8_t> hkdf_info = InitHkdfInfo(HKDF_CONTEXT_PER_FILE_ENC_KEY);

  hkdf_info.insert(hkdf_info.end(), nonce.bytes, std::end(nonce.bytes));

  return DeriveKey(master_key, hkdf_info, enc_key);
}

// For IV_INO_LBLK_32: Hashes the |inode_number| using the SipHash key derived
// from |master_key|.  Returns the resulting hash in |hash|.
static bool HashInodeNumber(const std::vector<uint8_t> &master_key,
                            uint64_t inode_number, uint32_t *hash) {
  union {
    uint64_t words[2];
    __le64 le_words[2];
  } siphash_key;
  union {
    __le64 inode_number;
    uint8_t bytes[8];
  } input;

  std::vector<uint8_t> hkdf_info = InitHkdfInfo(HKDF_CONTEXT_INODE_HASH_KEY);
  std::vector<uint8_t> ino_hash_key(sizeof(siphash_key));
  if (!DeriveKey(master_key, hkdf_info, ino_hash_key)) return false;

  memcpy(&siphash_key, &ino_hash_key[0], sizeof(siphash_key));
  siphash_key.words[0] = __le64_to_cpu(siphash_key.le_words[0]);
  siphash_key.words[1] = __le64_to_cpu(siphash_key.le_words[1]);

  GTEST_LOG_(INFO) << "Inode hash key is {" << std::hex << "0x"
                   << siphash_key.words[0] << ", 0x" << siphash_key.words[1]
                   << "}" << std::dec;

  input.inode_number = __cpu_to_le64(inode_number);

  *hash = SIPHASH_24(siphash_key.words, input.bytes, sizeof(input));
  GTEST_LOG_(INFO) << "Hashed inode number " << inode_number << " to 0x"
                   << std::hex << *hash << std::dec;
  return true;
}

void FBEPolicyTest::VerifyCiphertext(const std::vector<uint8_t> &enc_key,
                                     const FscryptIV &starting_iv,
                                     const Cipher &cipher,
                                     const TestFileInfo &file_info) {
  const std::vector<uint8_t> &plaintext = file_info.plaintext;

  GTEST_LOG_(INFO) << "Verifying correctness of encrypted data";
  FscryptIV iv = starting_iv;

  std::vector<uint8_t> computed_ciphertext(plaintext.size());

  // Encrypt each filesystem block of file contents.
  for (size_t i = 0; i < plaintext.size(); i += kFilesystemBlockSize) {
    int block_size =
        std::min<size_t>(kFilesystemBlockSize, plaintext.size() - i);

    ASSERT_GE(sizeof(iv.bytes), cipher.ivsize());
    ASSERT_TRUE(cipher.Encrypt(enc_key, iv.bytes, &plaintext[i],
                               &computed_ciphertext[i], block_size));

    // Update the IV by incrementing the file logical block number.
    iv.lblk_num = __cpu_to_le32(__le32_to_cpu(iv.lblk_num) + 1);
  }

  ASSERT_EQ(file_info.actual_ciphertext, computed_ciphertext);
}

static bool InitIVForPerFileKey(FscryptIV *iv) {
  memset(iv, 0, kFscryptMaxIVSize);
  return true;
}

static bool InitIVForDirectKey(const FscryptFileNonce &nonce, FscryptIV *iv) {
  memset(iv, 0, kFscryptMaxIVSize);
  memcpy(iv->file_nonce, nonce.bytes, kFscryptFileNonceSize);
  return true;
}

static bool InitIVForInoLblk64(uint64_t inode_number, FscryptIV *iv) {
  if (inode_number > UINT32_MAX) {
    ADD_FAILURE() << "inode number doesn't fit in 32 bits";
    return false;
  }
  memset(iv, 0, kFscryptMaxIVSize);
  iv->inode_number = __cpu_to_le32(inode_number);
  return true;
}

static bool InitIVForInoLblk32(const std::vector<uint8_t> &master_key,
                               uint64_t inode_number, FscryptIV *iv) {
  uint32_t hash;
  if (!HashInodeNumber(master_key, inode_number, &hash)) return false;
  memset(iv, 0, kFscryptMaxIVSize);
  iv->lblk_num = __cpu_to_le32(hash);
  return true;
}

// Tests a policy matching "fileencryption=aes-256-xts:aes-256-cts:v2"
// (or simply "fileencryption=" on devices launched with R or higher)
TEST_F(FBEPolicyTest, TestAesPerFileKeysPolicy) {
  if (skip_test_) return;

  auto master_key = GenerateTestKey(kFscryptMasterKeySize);
  ASSERT_TRUE(SetMasterKey(master_key));

  if (!SetEncryptionPolicy(FSCRYPT_MODE_AES_256_XTS, FSCRYPT_MODE_AES_256_CTS,
                           0, 0))
    return;

  TestFileInfo file_info;
  ASSERT_TRUE(GenerateTestFile(&file_info));

  std::vector<uint8_t> enc_key(kAes256XtsKeySize);
  ASSERT_TRUE(DerivePerFileEncryptionKey(master_key, file_info.nonce, enc_key));

  FscryptIV iv;
  ASSERT_TRUE(InitIVForPerFileKey(&iv));
  VerifyCiphertext(enc_key, iv, Aes256XtsCipher(), file_info);
}

// Tests a policy matching
// "fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized"
// (or simply "fileencryption=::inlinecrypt_optimized" on devices launched with
// R or higher)
TEST_F(FBEPolicyTest, TestAesInlineCryptOptimizedPolicy) {
  if (skip_test_) return;

  auto master_key = GenerateTestKey(kFscryptMasterKeySize);
  ASSERT_TRUE(SetMasterKey(master_key));

  if (!SetEncryptionPolicy(FSCRYPT_MODE_AES_256_XTS, FSCRYPT_MODE_AES_256_CTS,
                           FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64,
                           GetSkipFlagsForInoBasedEncryption()))
    return;

  TestFileInfo file_info;
  ASSERT_TRUE(GenerateTestFile(&file_info));

  std::vector<uint8_t> enc_key(kAes256XtsKeySize);
  ASSERT_TRUE(DerivePerModeEncryptionKey(master_key, FSCRYPT_MODE_AES_256_XTS,
                                         HKDF_CONTEXT_IV_INO_LBLK_64_KEY,
                                         enc_key));

  FscryptIV iv;
  ASSERT_TRUE(InitIVForInoLblk64(file_info.inode_number, &iv));
  VerifyCiphertext(enc_key, iv, Aes256XtsCipher(), file_info);
}

// Tests a policy matching
// "fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized+wrappedkey_v0"
// (or simply "fileencryption=::inlinecrypt_optimized+wrappedkey_v0" on devices
// launched with R or higher)
TEST_F(FBEPolicyTest, TestAesInlineCryptOptimizedHwWrappedKeyPolicy) {
  if (skip_test_) return;

  std::vector<uint8_t> enc_key, sw_secret;
  if (!CreateAndSetHwWrappedKey(&enc_key, &sw_secret)) return;

  if (!SetEncryptionPolicy(FSCRYPT_MODE_AES_256_XTS, FSCRYPT_MODE_AES_256_CTS,
                           FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64,
                           // 64-bit DUN support is not guaranteed.
                           kSkipIfInlineEncryptionNotUsable |
                               GetSkipFlagsForInoBasedEncryption()))
    return;

  TestFileInfo file_info;
  ASSERT_TRUE(GenerateTestFile(&file_info));

  FscryptIV iv;
  ASSERT_TRUE(InitIVForInoLblk64(file_info.inode_number, &iv));
  VerifyCiphertext(enc_key, iv, Aes256XtsCipher(), file_info);
}

// With IV_INO_LBLK_32, the DUN (IV) can wrap from UINT32_MAX to 0 in the middle
// of the file.  This method tests that this case appears to be handled
// correctly, by doing I/O across the place where the DUN wraps around.  Assumes
// that test_dir_ has already been set up with an IV_INO_LBLK_32 policy.
void FBEPolicyTest::TestEmmcOptimizedDunWraparound(
    const std::vector<uint8_t> &master_key,
    const std::vector<uint8_t> &enc_key) {
  // We'll test writing 'block_count' filesystem blocks.  The first
  // 'block_count_1' blocks will have DUNs [..., UINT32_MAX - 1, UINT32_MAX].
  // The remaining 'block_count_2' blocks will have DUNs [0, 1, ...].
  constexpr uint32_t block_count_1 = 3;
  constexpr uint32_t block_count_2 = 7;
  constexpr uint32_t block_count = block_count_1 + block_count_2;
  constexpr size_t data_size = block_count * kFilesystemBlockSize;

  // Assumed maximum file size.  Unfortunately there isn't a syscall to get
  // this.  ext4 allows ~16TB and f2fs allows ~4TB.  However, an underestimate
  // works fine for our purposes, so just go with 1TB.
  constexpr off_t max_file_size = 1000000000000;
  constexpr off_t max_file_blocks = max_file_size / kFilesystemBlockSize;

  // Repeatedly create empty files until we find one that can be used for DUN
  // wraparound testing, due to SipHash(inode_number) being almost UINT32_MAX.
  std::string path;
  TestFileInfo file_info;
  uint32_t lblk_with_dun_0;
  for (int i = 0;; i++) {
    // The probability of finding a usable file is about 'max_file_blocks /
    // UINT32_MAX', or about 5.6%.  So on average we'll need about 18 tries.
    // The probability we'll need over 1000 tries is less than 1e-25.
    ASSERT_LT(i, 1000) << "Tried too many times to find a usable test file";

    path = android::base::StringPrintf("%s/file%d", test_dir_.c_str(), i);
    android::base::unique_fd fd(
        open(path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600));
    ASSERT_GE(fd, 0) << "Failed to create " << path << Errno();

    ASSERT_TRUE(GetInodeNumber(path, &file_info.inode_number));
    uint32_t hash;
    ASSERT_TRUE(HashInodeNumber(master_key, file_info.inode_number, &hash));
    // Negating the hash gives the distance to DUN 0, and hence the 0-based
    // logical block number of the block which has DUN 0.
    lblk_with_dun_0 = -hash;
    if (lblk_with_dun_0 >= block_count_1 &&
        static_cast<off_t>(lblk_with_dun_0) + block_count_2 < max_file_blocks)
      break;
  }

  GTEST_LOG_(INFO) << "DUN wraparound test: path=" << path
                   << ", inode_number=" << file_info.inode_number
                   << ", lblk_with_dun_0=" << lblk_with_dun_0;

  // Write some data across the DUN wraparound boundary and verify that the
  // resulting on-disk ciphertext is as expected.  Note that we don't actually
  // have to fill the file until the boundary; we can just write to the needed
  // part and leave a hole before it.
  for (int i = 0; i < 2; i++) {
    // Try both buffered I/O and direct I/O.
    int open_flags = O_RDWR | O_CLOEXEC;
    if (i == 1) open_flags |= O_DIRECT;

    android::base::unique_fd fd(open(path.c_str(), open_flags));
    ASSERT_GE(fd, 0) << "Failed to open " << path << Errno();

    // Generate some test data.
    file_info.plaintext.resize(data_size);
    RandomBytesForTesting(file_info.plaintext);

    // Write the test data.  To support O_DIRECT, use a block-aligned buffer.
    std::unique_ptr<void, void (*)(void *)> buf_mem(
        aligned_alloc(kFilesystemBlockSize, data_size), free);
    ASSERT_TRUE(buf_mem != nullptr);
    memcpy(buf_mem.get(), &file_info.plaintext[0], data_size);
    off_t pos = static_cast<off_t>(lblk_with_dun_0 - block_count_1) *
                kFilesystemBlockSize;
    ASSERT_EQ(data_size, pwrite(fd, buf_mem.get(), data_size, pos))
        << "Error writing data to " << path << Errno();

    // Verify the ciphertext.
    ASSERT_TRUE(ReadRawDataOfFile(fd, fs_info_, data_size,
                                  &file_info.actual_ciphertext));
    FscryptIV iv;
    memset(&iv, 0, sizeof(iv));
    iv.lblk_num = __cpu_to_le32(-block_count_1);
    VerifyCiphertext(enc_key, iv, Aes256XtsCipher(), file_info);
  }
}

// Tests a policy matching
// "fileencryption=aes-256-xts:aes-256-cts:v2+emmc_optimized" (or simply
// "fileencryption=::emmc_optimized" on devices launched with R or higher)
TEST_F(FBEPolicyTest, TestAesEmmcOptimizedPolicy) {
  if (skip_test_) return;

  auto master_key = GenerateTestKey(kFscryptMasterKeySize);
  ASSERT_TRUE(SetMasterKey(master_key));

  if (!SetEncryptionPolicy(FSCRYPT_MODE_AES_256_XTS, FSCRYPT_MODE_AES_256_CTS,
                           FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32,
                           GetSkipFlagsForInoBasedEncryption()))
    return;

  TestFileInfo file_info;
  ASSERT_TRUE(GenerateTestFile(&file_info));

  std::vector<uint8_t> enc_key(kAes256XtsKeySize);
  ASSERT_TRUE(DerivePerModeEncryptionKey(master_key, FSCRYPT_MODE_AES_256_XTS,
                                         HKDF_CONTEXT_IV_INO_LBLK_32_KEY,
                                         enc_key));

  FscryptIV iv;
  ASSERT_TRUE(InitIVForInoLblk32(master_key, file_info.inode_number, &iv));
  VerifyCiphertext(enc_key, iv, Aes256XtsCipher(), file_info);

  TestEmmcOptimizedDunWraparound(master_key, enc_key);
}

// Tests a policy matching
// "fileencryption=aes-256-xts:aes-256-cts:v2+emmc_optimized+wrappedkey_v0"
// (or simply "fileencryption=::emmc_optimized+wrappedkey_v0" on devices
// launched with R or higher)
TEST_F(FBEPolicyTest, TestAesEmmcOptimizedHwWrappedKeyPolicy) {
  if (skip_test_) return;

  std::vector<uint8_t> enc_key, sw_secret;
  if (!CreateAndSetHwWrappedKey(&enc_key, &sw_secret)) return;

  int skip_flags = GetSkipFlagsForInoBasedEncryption();
  if (kFilesystemBlockSize != getpagesize())
    skip_flags |= kSkipIfInlineEncryptionNotUsable;

  if (!SetEncryptionPolicy(FSCRYPT_MODE_AES_256_XTS, FSCRYPT_MODE_AES_256_CTS,
                           FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32, skip_flags))
    return;

  TestFileInfo file_info;
  ASSERT_TRUE(GenerateTestFile(&file_info));

  FscryptIV iv;
  ASSERT_TRUE(InitIVForInoLblk32(sw_secret, file_info.inode_number, &iv));
  VerifyCiphertext(enc_key, iv, Aes256XtsCipher(), file_info);

  TestEmmcOptimizedDunWraparound(sw_secret, enc_key);
}

// Tests a policy matching "fileencryption=adiantum:adiantum:v2" (or simply
// "fileencryption=adiantum" on devices launched with R or higher)
TEST_F(FBEPolicyTest, TestAdiantumPolicy) {
  if (skip_test_) return;

  auto master_key = GenerateTestKey(kFscryptMasterKeySize);
  ASSERT_TRUE(SetMasterKey(master_key));

  // Adiantum support isn't required (since CONFIG_CRYPTO_ADIANTUM can be unset
  // in the kernel config), so we may skip the test here.
  //
  // We don't need to use GetSkipFlagsForInoBasedEncryption() here, since the
  // "DIRECT_KEY" IV generation method doesn't include inode numbers in the IVs.
  if (!SetEncryptionPolicy(FSCRYPT_MODE_ADIANTUM, FSCRYPT_MODE_ADIANTUM,
                           FSCRYPT_POLICY_FLAG_DIRECT_KEY,
                           kSkipIfNoCryptoAPISupport))
    return;

  TestFileInfo file_info;
  ASSERT_TRUE(GenerateTestFile(&file_info));

  std::vector<uint8_t> enc_key(kAdiantumKeySize);
  ASSERT_TRUE(DerivePerModeEncryptionKey(master_key, FSCRYPT_MODE_ADIANTUM,
                                         HKDF_CONTEXT_DIRECT_KEY, enc_key));

  FscryptIV iv;
  ASSERT_TRUE(InitIVForDirectKey(file_info.nonce, &iv));
  VerifyCiphertext(enc_key, iv, AdiantumCipher(), file_info);
}

// Tests adding a corrupted wrapped key to fscrypt keyring.
// If wrapped key is corrupted, fscrypt should return a failure.
TEST_F(FBEPolicyTest, TestHwWrappedKeyCorruption) {
  if (skip_test_) return;

  std::vector<uint8_t> master_key, exported_key;
  if (!CreateHwWrappedKey(&master_key, &exported_key)) return;

  for (int i = 0; i < exported_key.size(); i++) {
    std::vector<uint8_t> corrupt_key(exported_key.begin(), exported_key.end());
    corrupt_key[i] = ~corrupt_key[i];
    ASSERT_FALSE(
        SetMasterKey(corrupt_key, __FSCRYPT_ADD_KEY_FLAG_HW_WRAPPED, false));
  }
}

bool FBEPolicyTest::EnableF2fsCompressionOnTestDir() {
  android::base::unique_fd fd(open(test_dir_.c_str(), O_RDONLY | O_CLOEXEC));
  if (fd < 0) {
    ADD_FAILURE() << "Failed to open " << test_dir_ << Errno();
    return false;
  }

  int flags;
  if (ioctl(fd, FS_IOC_GETFLAGS, &flags) != 0) {
    ADD_FAILURE() << "Unexpected error getting flags of " << test_dir_
                  << Errno();
    return false;
  }
  flags |= FS_COMPR_FL;
  if (ioctl(fd, FS_IOC_SETFLAGS, &flags) != 0) {
    if (errno == EOPNOTSUPP) {
      GTEST_LOG_(INFO)
          << "Skipping test because f2fs compression is not supported on "
          << kTestMountpoint;
      return false;
    }
    ADD_FAILURE() << "Unexpected error enabling compression on " << test_dir_
                  << Errno();
    return false;
  }
  return true;
}

static std::string F2fsCompressAlgorithmName(int algorithm) {
  switch (algorithm) {
    case F2FS_COMPRESS_LZO:
      return "LZO";
    case F2FS_COMPRESS_LZ4:
      return "LZ4";
    case F2FS_COMPRESS_ZSTD:
      return "ZSTD";
    case F2FS_COMPRESS_LZORLE:
      return "LZORLE";
    default:
      return android::base::StringPrintf("%d", algorithm);
  }
}

bool FBEPolicyTest::F2fsCompressOptionsSupported(
    const struct f2fs_comp_option &opts) {
  android::base::unique_fd fd(
      open(test_file_.c_str(), O_WRONLY | O_CREAT, 0600));
  if (fd < 0) {
    // If the filesystem has the compression feature flag enabled but f2fs
    // compression support was compiled out of the kernel, then setting
    // FS_COMPR_FL on the directory will succeed, but creating a file in the
    // directory will fail with EOPNOTSUPP.
    if (errno == EOPNOTSUPP) {
      GTEST_LOG_(INFO)
          << "Skipping test because kernel doesn't support f2fs compression";
      return false;
    }
    ADD_FAILURE() << "Unexpected error creating " << test_file_
                  << " after enabling f2fs compression on parent directory"
                  << Errno();
    return false;
  }

  if (ioctl(fd, F2FS_IOC_SET_COMPRESS_OPTION, &opts) != 0) {
    if (errno == ENOTTY || errno == EOPNOTSUPP) {
      GTEST_LOG_(INFO) << "Skipping test because kernel doesn't support "
                          "F2FS_IOC_SET_COMPRESS_OPTION on "
                       << kTestMountpoint;
      return false;
    }
    ADD_FAILURE() << "Unexpected error from F2FS_IOC_SET_COMPRESS_OPTION"
                  << Errno();
    return false;
  }
  // Unsupported compression algorithms aren't detected until the file is
  // reopened.
  fd.reset(open(test_file_.c_str(), O_WRONLY));
  if (fd < 0) {
    if (errno == EOPNOTSUPP || errno == ENOPKG) {
      GTEST_LOG_(INFO) << "Skipping test because kernel doesn't support "
                       << F2fsCompressAlgorithmName(opts.algorithm)
                       << " compression";
      return false;
    }
    ADD_FAILURE() << "Unexpected error when reopening file after "
                     "F2FS_IOC_SET_COMPRESS_OPTION"
                  << Errno();
    return false;
  }
  unlink(test_file_.c_str());
  return true;
}

// Tests that encryption is done correctly on compressed files.
//
// This works by creating a compressed+encrypted file, then decrypting the
// file's on-disk data, then decompressing it, then comparing the result to the
// original data.  We don't do it the other way around (compress+encrypt the
// original data and compare to the on-disk data) because different
// implementations of a compression algorithm can produce different results.
//
// This is adapted from the xfstest "f2fs/002"; see there for some more details.
//
// This test will skip itself if any of the following is true:
//   - f2fs compression isn't enabled on /data
//   - f2fs compression isn't enabled in the kernel (CONFIG_F2FS_FS_COMPRESSION)
//   - The kernel doesn't support the needed algorithm (CONFIG_F2FS_FS_LZ4)
//   - The kernel doesn't support the F2FS_IOC_SET_COMPRESS_OPTION ioctl
//
// Note, this test will be flaky if the kernel is missing commit 093f0bac32b
// ("f2fs: change fiemap way in printing compression chunk").
//
// This test is currently disabled because the test is still flaky even with the
// above fix, and it hasn't been able to be root-caused.  TODO(b/329449658):
// root cause the issue and re-enable the test.
TEST_F(FBEPolicyTest, DISABLED_TestF2fsCompression) {
  if (skip_test_) return;

  // Currently, only f2fs supports compression+encryption.
  if (fs_info_.type != "f2fs") {
    GTEST_LOG_(INFO) << "Skipping test because device uses " << fs_info_.type
                     << ", not f2fs";
    return;
  }

  // Enable compression and encryption on the test directory.  Afterwards, both
  // of these features will be inherited by any file created in this directory.
  //
  // If compression is not supported, skip the test.  Use the default encryption
  // settings, which should always be supported.
  if (!EnableF2fsCompressionOnTestDir()) return;
  auto master_key = GenerateTestKey(kFscryptMasterKeySize);
  ASSERT_TRUE(SetMasterKey(master_key));
  ASSERT_TRUE(SetEncryptionPolicy(FSCRYPT_MODE_AES_256_XTS,
                                  FSCRYPT_MODE_AES_256_CTS, 0, 0));

  // This test will use LZ4 compression with a cluster size of 2^2 = 4 blocks.
  // Check that this setting is supported.
  //
  // Note that the precise choice of algorithm and cluster size isn't too
  // important for this test.  We just (somewhat arbitrarily) chose a setting
  // which is commonly used and for which a decompression library is available.
  const int log_cluster_size = 2;
  const int cluster_bytes = kFilesystemBlockSize << log_cluster_size;
  struct f2fs_comp_option comp_opt;
  memset(&comp_opt, 0, sizeof(comp_opt));
  comp_opt.algorithm = F2FS_COMPRESS_LZ4;
  comp_opt.log_cluster_size = log_cluster_size;
  if (!F2fsCompressOptionsSupported(comp_opt)) return;

  // Generate the test file and retrieve its on-disk data.  Note: despite being
  // compressed, the on-disk data here will still be |kTestFileBytes| long.
  // This is because FS_IOC_FIEMAP doesn't natively support compression, and the
  // way that f2fs handles it on compressed files results in us reading extra
  // blocks appended to the compressed clusters.  It works out in the end
  // though, since these extra blocks get ignored during decompression.
  TestFileInfo file_info;
  ASSERT_TRUE(GenerateTestFile(&file_info, &comp_opt));

  GTEST_LOG_(INFO) << "Decrypting the blocks of the compressed file";
  std::vector<uint8_t> enc_key(kAes256XtsKeySize);
  ASSERT_TRUE(DerivePerFileEncryptionKey(master_key, file_info.nonce, enc_key));
  std::vector<uint8_t> decrypted_data(kTestFileBytes);
  FscryptIV iv;
  memset(&iv, 0, sizeof(iv));
  ASSERT_EQ(0, kTestFileBytes % kFilesystemBlockSize);
  for (int i = 0; i < kTestFileBytes; i += kFilesystemBlockSize) {
    int block_num = i / kFilesystemBlockSize;
    int cluster_num = i / cluster_bytes;

    // In compressed clusters, IVs start at 1 higher than the expected value.
    // Fortunately, due to the compression there is no overlap...
    if (IsCompressibleCluster(cluster_num)) block_num++;

    iv.lblk_num = __cpu_to_le32(block_num);
    ASSERT_TRUE(Aes256XtsCipher().Decrypt(
        enc_key, iv.bytes, &file_info.actual_ciphertext[i], &decrypted_data[i],
        kFilesystemBlockSize));
  }

  GTEST_LOG_(INFO) << "Decompressing the decrypted blocks of the file";
  std::vector<uint8_t> decompressed_data(kTestFileBytes);
  ASSERT_EQ(0, kTestFileBytes % cluster_bytes);
  for (int i = 0; i < kTestFileBytes; i += cluster_bytes) {
    int cluster_num = i / cluster_bytes;
    if (IsCompressibleCluster(cluster_num)) {
      // We had filled this cluster with compressible data, so it should have
      // been stored compressed.
      ASSERT_TRUE(DecompressLZ4Cluster(&decrypted_data[i],
                                       &decompressed_data[i], cluster_bytes));
    } else {
      // We had filled this cluster with random data, so it should have been
      // incompressible and thus stored uncompressed.
      memcpy(&decompressed_data[i], &decrypted_data[i], cluster_bytes);
    }
  }

  // Finally do the actual test.  The data we got after decryption+decompression
  // should match the original file contents.
  GTEST_LOG_(INFO) << "Comparing the result to the original data";
  ASSERT_EQ(file_info.plaintext, decompressed_data);
}

static bool DeviceUsesFBE() {
  if (android::base::GetProperty("ro.crypto.type", "") == "file") return true;
  // FBE has been required since Android Q.
  int first_api_level;
  if (!GetFirstApiLevel(&first_api_level)) return true;
  if (first_api_level >= __ANDROID_API_Q__) {
    ADD_FAILURE() << "File-based encryption is required";
  } else {
    GTEST_LOG_(INFO)
        << "Skipping test because device doesn't use file-based encryption";
  }
  return false;
}

// Retrieves the encryption key specifier used in the file-based encryption
// policy of |dir|.  This isn't the key itself, but rather a "name" for the key.
// If the key specifier cannot be retrieved, e.g. due to the directory being
// unencrypted, then false is returned and a failure is added.
static bool GetKeyUsedByDir(const std::string &dir,
                            std::string *key_specifier) {
  android::base::unique_fd fd(open(dir.c_str(), O_RDONLY));
  if (fd < 0) {
    ADD_FAILURE() << "Failed to open " << dir << Errno();
    return false;
  }
  struct fscrypt_get_policy_ex_arg arg = {.policy_size = sizeof(arg.policy)};
  int res = ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY_EX, &arg);
  if (res != 0 && errno == ENOTTY) {
    // Handle old kernels that don't support FS_IOC_GET_ENCRYPTION_POLICY_EX.
    res = ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY, &arg.policy.v1);
  }
  if (res != 0) {
    if (errno == ENODATA) {
      ADD_FAILURE() << "Directory " << dir << " is not encrypted!";
    } else {
      ADD_FAILURE() << "Failed to get encryption policy of " << dir << Errno();
    }
    return false;
  }
  switch (arg.policy.version) {
    case FSCRYPT_POLICY_V1:
      *key_specifier = BytesToHex(arg.policy.v1.master_key_descriptor);
      return true;
    case FSCRYPT_POLICY_V2:
      *key_specifier = BytesToHex(arg.policy.v2.master_key_identifier);
      return true;
    default:
      ADD_FAILURE() << dir << " uses unknown encryption policy version ("
                    << arg.policy.version << ")";
      return false;
  }
}

// Tests that if the device uses FBE, then the ciphertext for file contents in
// encrypted directories seems to be random.
//
// This isn't as strong a test as the correctness tests, but it's useful because
// it applies regardless of the encryption format and key.  Thus it runs even on
// old devices, including ones that used a vendor-specific encryption format.
TEST(FBETest, TestFileContentsRandomness) {
  const std::string path_1 =
      android::base::StringPrintf("%s/FBETest-1.%d", kTmpDir, getpid());
  const std::string path_2 =
      android::base::StringPrintf("%s/FBETest-2.%d", kTmpDir, getpid());

  if (!DeviceUsesFBE()) return;

  FilesystemInfo fs_info;
  ASSERT_TRUE(GetFilesystemInfo(kTestMountpoint, &fs_info));

  std::vector<uint8_t> zeroes(kTestFileBytes, 0);
  std::vector<uint8_t> ciphertext_1;
  std::vector<uint8_t> ciphertext_2;
  ASSERT_TRUE(WriteTestFile(zeroes, path_1, fs_info, nullptr, &ciphertext_1));
  ASSERT_TRUE(WriteTestFile(zeroes, path_2, fs_info, nullptr, &ciphertext_2));

  GTEST_LOG_(INFO) << "Verifying randomness of ciphertext";

  // Each individual file's ciphertext should be random.
  ASSERT_TRUE(VerifyDataRandomness(ciphertext_1));
  ASSERT_TRUE(VerifyDataRandomness(ciphertext_2));

  // The files' ciphertext concatenated should also be random.
  // I.e., each file should be encrypted differently.
  std::vector<uint8_t> concatenated_ciphertext;
  concatenated_ciphertext.insert(concatenated_ciphertext.end(),
                                 ciphertext_1.begin(), ciphertext_1.end());
  concatenated_ciphertext.insert(concatenated_ciphertext.end(),
                                 ciphertext_2.begin(), ciphertext_2.end());
  ASSERT_TRUE(VerifyDataRandomness(concatenated_ciphertext));

  ASSERT_EQ(unlink(path_1.c_str()), 0);
  ASSERT_EQ(unlink(path_2.c_str()), 0);
}

// Tests that all of user 0's directories that should be encrypted actually are,
// and that user 0's CE and DE keys are different.
TEST(FBETest, TestUserDirectoryPolicies) {
  if (!DeviceUsesFBE()) return;

  std::string user0_ce_key, user0_de_key;
  EXPECT_TRUE(GetKeyUsedByDir("/data/user/0", &user0_ce_key));
  EXPECT_TRUE(GetKeyUsedByDir("/data/user_de/0", &user0_de_key));
  EXPECT_NE(user0_ce_key, user0_de_key) << "CE and DE keys must differ";

  // Check the CE directories other than /data/user/0.
  for (const std::string &dir : {"/data/media/0", "/data/misc_ce/0",
                                 "/data/system_ce/0", "/data/vendor_ce/0"}) {
    std::string key;
    EXPECT_TRUE(GetKeyUsedByDir(dir, &key));
    EXPECT_EQ(key, user0_ce_key) << dir << " must be encrypted with CE key";
  }

  // Check the DE directories other than /data/user_de/0.
  for (const std::string &dir :
       {"/data/misc_de/0", "/data/system_de/0", "/data/vendor_de/0"}) {
    std::string key;
    EXPECT_TRUE(GetKeyUsedByDir(dir, &key));
    EXPECT_EQ(key, user0_de_key) << dir << " must be encrypted with DE key";
  }
}

}  // namespace kernel
}  // namespace android
