Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a3b72067 authored by Keith Mok's avatar Keith Mok
Browse files

Add checking for sparse file format

Sparse file can come from an untrusted source.
Need more checking to ensure that it is not a malformed
file and would not cause any OOB read access.

Update fuzz test for decoding also.

Test: adb reboot fastboot
      fuzzy_fastboot --gtest_filter=Fuzz.Sparse*
      fuzzy_fastboot --gtest_filter=Conformance.Sparse*
      sparse_fuzzer
Bug: 212705418
Change-Id: I7622df307bb00e59faaba8bb2c67cb474cffed8e
parent e060580c
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -119,9 +119,11 @@ int WriteCallback(void* priv, const void* data, size_t len) {
}

int FlashSparseData(int fd, std::vector<char>& downloaded_data) {
    struct sparse_file* file = sparse_file_import_buf(downloaded_data.data(), true, false);
    struct sparse_file* file = sparse_file_import_buf(downloaded_data.data(),
                                                      downloaded_data.size(), true, false);
    if (!file) {
        return -ENOENT;
        // Invalid sparse format
        return -EINVAL;
    }
    return sparse_file_callback(file, false, false, WriteCallback, reinterpret_cast<void*>(fd));
}
+57 −0
Original line number Diff line number Diff line
@@ -977,6 +977,63 @@ TEST_F(Fuzz, SparseZeroLength) {
    }
}

TEST_F(Fuzz, SparseZeroBlkSize) {
    // handcrafted malform sparse file with zero as block size
    const std::vector<char> buf = {
        '\x3a', '\xff', '\x26', '\xed', '\x01', '\x00', '\x00', '\x00', '\x1c', '\x00', '\x0c', '\x00',
        '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00',
        '\x00', '\x00', '\x00', '\x00', '\xc2', '\xca', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00',
        '\x10', '\x00', '\x00', '\x00', '\x11', '\x22', '\x33', '\x44'
    };

    ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command";
    ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed";

    // It can either reject this download or reject it during flash
    if (HandleResponse() != DEVICE_FAIL) {
        EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL)
                << "Flashing a zero block size in sparse file should fail";
    }
}

TEST_F(Fuzz, SparseTrimmed) {
    // handcrafted malform sparse file which is trimmed
    const std::vector<char> buf = {
        '\x3a', '\xff', '\x26', '\xed', '\x01', '\x00', '\x00', '\x00', '\x1c', '\x00', '\x0c', '\x00',
        '\x00', '\x10', '\x00', '\x00', '\x00', '\x00', '\x08', '\x00', '\x01', '\x00', '\x00', '\x00',
        '\x00', '\x00', '\x00', '\x00', '\xc1', '\xca', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00',
        '\x00', '\x00', '\x00', '\x80', '\x11', '\x22', '\x33', '\x44'
    };

    ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command";
    ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed";

    // It can either reject this download or reject it during flash
    if (HandleResponse() != DEVICE_FAIL) {
        EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL)
                << "Flashing a trimmed sparse file should fail";
    }
}

TEST_F(Fuzz, SparseInvalidChurk) {
    // handcrafted malform sparse file with invalid churk
    const std::vector<char> buf = {
        '\x3a', '\xff', '\x26', '\xed', '\x01', '\x00', '\x00', '\x00', '\x1c', '\x00', '\x0c', '\x00',
        '\x00', '\x10', '\x00', '\x00', '\x00', '\x00', '\x08', '\x00', '\x01', '\x00', '\x00', '\x00',
        '\x00', '\x00', '\x00', '\x00', '\xc1', '\xca', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00',
        '\x10', '\x00', '\x00', '\x00', '\x11', '\x22', '\x33', '\x44'
    };

    ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command";
    ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed";

    // It can either reject this download or reject it during flash
    if (HandleResponse() != DEVICE_FAIL) {
        EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL)
                << "Flashing a sparse file with invalid churk should fail";
    }
}

TEST_F(Fuzz, SparseTooManyChunks) {
    SparseWrapper sparse(4096, 4096);  // 1 block, but we send two chunks that will use 2 blocks
    ASSERT_TRUE(*sparse) << "Sparse image creation failed";
+3 −1
Original line number Diff line number Diff line
@@ -96,12 +96,14 @@ python_binary_host {

cc_fuzz {
    name: "sparse_fuzzer",
    host_supported: false,
    host_supported: true,
    srcs: [
        "sparse_fuzzer.cpp",
    ],
    static_libs: [
        "libsparse",
        "libbase",
        "libz",
        "liblog",
    ],
}
+2 −16
Original line number Diff line number Diff line
@@ -243,21 +243,6 @@ int sparse_file_foreach_chunk(struct sparse_file *s, bool sparse, bool crc,
 */
int sparse_file_read(struct sparse_file *s, int fd, bool sparse, bool crc);

/**
 * sparse_file_read_buf - read a buffer into a sparse file cookie
 *
 * @s - sparse file cookie
 * @buf - buffer to read from
 * @crc - verify the crc of a file in the Android sparse file format
 *
 * Reads a buffer into a sparse file cookie. The buffer must remain
 * valid until the sparse file cookie is freed. If crc is true, the
 * crc of the sparse file will be verified.
 *
 * Returns 0 on success, negative errno on error.
 */
int sparse_file_read_buf(struct sparse_file *s, char *buf, bool crc);

/**
 * sparse_file_import - import an existing sparse file
 *
@@ -277,6 +262,7 @@ struct sparse_file *sparse_file_import(int fd, bool verbose, bool crc);
 * sparse_file_import_buf - import an existing sparse file from a buffer
 *
 * @buf - buffer to read from
 * @len - length of buffer
 * @verbose - print verbose errors while reading the sparse file
 * @crc - verify the crc of a file in the Android sparse file format
 *
@@ -286,7 +272,7 @@ struct sparse_file *sparse_file_import(int fd, bool verbose, bool crc);
 *
 * Returns a new sparse file cookie on success, NULL on error.
 */
struct sparse_file *sparse_file_import_buf(char* buf, bool verbose, bool crc);
struct sparse_file* sparse_file_import_buf(char* buf, size_t len, bool verbose, bool crc);

/**
 * sparse_file_import_auto - import an existing sparse or normal file
+27 −7
Original line number Diff line number Diff line
@@ -54,6 +54,8 @@
#define SPARSE_HEADER_LEN (sizeof(sparse_header_t))
#define CHUNK_HEADER_LEN (sizeof(chunk_header_t))

#define FILL_ZERO_BUFSIZE (2 * 1024 * 1024)

#define container_of(inner, outer_t, elem) ((outer_t*)((char*)(inner)-offsetof(outer_t, elem)))

struct output_file_ops {
@@ -391,13 +393,29 @@ static int write_sparse_data_chunk(struct output_file* out, uint64_t len, void*
  ret = out->ops->write(out, data, len);
  if (ret < 0) return -1;
  if (zero_len) {
    ret = out->ops->write(out, out->zero_buf, zero_len);
    if (ret < 0) return -1;
    uint64_t len = zero_len;
    uint64_t write_len;
    while (len) {
      write_len = std::min(len, (uint64_t)FILL_ZERO_BUFSIZE);
      ret = out->ops->write(out, out->zero_buf, write_len);
      if (ret < 0) {
        return ret;
      }
      len -= write_len;
    }
  }

  if (out->use_crc) {
    out->crc32 = sparse_crc32(out->crc32, data, len);
    if (zero_len) out->crc32 = sparse_crc32(out->crc32, out->zero_buf, zero_len);
    if (zero_len) {
      uint64_t len = zero_len;
      uint64_t write_len;
      while (len) {
        write_len = std::min(len, (uint64_t)FILL_ZERO_BUFSIZE);
        out->crc32 = sparse_crc32(out->crc32, out->zero_buf, write_len);
        len -= write_len;
      }
    }
  }

  out->cur_out_ptr += rnd_up_len;
@@ -460,12 +478,12 @@ static int write_normal_fill_chunk(struct output_file* out, uint64_t len, uint32
  uint64_t write_len;

  /* Initialize fill_buf with the fill_val */
  for (i = 0; i < out->block_size / sizeof(uint32_t); i++) {
  for (i = 0; i < FILL_ZERO_BUFSIZE / sizeof(uint32_t); i++) {
    out->fill_buf[i] = fill_val;
  }

  while (len) {
    write_len = std::min(len, (uint64_t)out->block_size);
    write_len = std::min(len, (uint64_t)FILL_ZERO_BUFSIZE);
    ret = out->ops->write(out, out->fill_buf, write_len);
    if (ret < 0) {
      return ret;
@@ -512,13 +530,15 @@ static int output_file_init(struct output_file* out, int block_size, int64_t len
  out->crc32 = 0;
  out->use_crc = crc;

  out->zero_buf = reinterpret_cast<char*>(calloc(block_size, 1));
  // don't use sparse format block size as it can takes up to 32GB
  out->zero_buf = reinterpret_cast<char*>(calloc(FILL_ZERO_BUFSIZE, 1));
  if (!out->zero_buf) {
    error_errno("malloc zero_buf");
    return -ENOMEM;
  }

  out->fill_buf = reinterpret_cast<uint32_t*>(calloc(block_size, 1));
  // don't use sparse format block size as it can takes up to 32GB
  out->fill_buf = reinterpret_cast<uint32_t*>(calloc(FILL_ZERO_BUFSIZE, 1));
  if (!out->fill_buf) {
    error_errno("malloc fill_buf");
    ret = -ENOMEM;
Loading