diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp index 7bef72af1..44dc81f4f 100644 --- a/fastboot/device/flashing.cpp +++ b/fastboot/device/flashing.cpp @@ -119,9 +119,11 @@ int WriteCallback(void* priv, const void* data, size_t len) { } int FlashSparseData(int fd, std::vector& 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(fd)); } diff --git a/fastboot/fuzzy_fastboot/main.cpp b/fastboot/fuzzy_fastboot/main.cpp index 8593adc87..055a0470a 100644 --- a/fastboot/fuzzy_fastboot/main.cpp +++ b/fastboot/fuzzy_fastboot/main.cpp @@ -977,6 +977,63 @@ TEST_F(Fuzz, SparseZeroLength) { } } +TEST_F(Fuzz, SparseZeroBlkSize) { + // handcrafted malform sparse file with zero as block size + const std::vector 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 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 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"; diff --git a/libsparse/Android.bp b/libsparse/Android.bp index 3f9aeb28a..02bfee68d 100644 --- a/libsparse/Android.bp +++ b/libsparse/Android.bp @@ -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", ], } diff --git a/libsparse/include/sparse/sparse.h b/libsparse/include/sparse/sparse.h index 2f75349a6..9f912697a 100644 --- a/libsparse/include/sparse/sparse.h +++ b/libsparse/include/sparse/sparse.h @@ -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 diff --git a/libsparse/output_file.cpp b/libsparse/output_file.cpp index b2c5407e1..cb5d73052 100644 --- a/libsparse/output_file.cpp +++ b/libsparse/output_file.cpp @@ -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(calloc(block_size, 1)); + // don't use sparse format block size as it can takes up to 32GB + out->zero_buf = reinterpret_cast(calloc(FILL_ZERO_BUFSIZE, 1)); if (!out->zero_buf) { error_errno("malloc zero_buf"); return -ENOMEM; } - out->fill_buf = reinterpret_cast(calloc(block_size, 1)); + // don't use sparse format block size as it can takes up to 32GB + out->fill_buf = reinterpret_cast(calloc(FILL_ZERO_BUFSIZE, 1)); if (!out->fill_buf) { error_errno("malloc fill_buf"); ret = -ENOMEM; diff --git a/libsparse/sparse_fuzzer.cpp b/libsparse/sparse_fuzzer.cpp index 42f331fc3..235d15dce 100644 --- a/libsparse/sparse_fuzzer.cpp +++ b/libsparse/sparse_fuzzer.cpp @@ -1,16 +1,27 @@ #include "include/sparse/sparse.h" -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - if (size < 2 * sizeof(wchar_t)) return 0; +static volatile int count; - int64_t blocksize = 4096; - struct sparse_file* file = sparse_file_new(size, blocksize); - if (!file) { +int WriteCallback(void* priv __attribute__((__unused__)), const void* data, size_t len) { + if (!data) { + return 0; + } + if (len == 0) { return 0; } - unsigned int block = 1; - sparse_file_add_data(file, &data, size, block); - sparse_file_destroy(file); + const char* p = (const char*)data; + // Just to make sure the data is accessible + // We only check the head and tail to save time + count += *p; + count += *(p+len-1); return 0; } + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + struct sparse_file* file = sparse_file_import_buf((char*)data, size, true, false); + if (!file) { + return 0; + } + return sparse_file_callback(file, false, false, WriteCallback, nullptr); +} diff --git a/libsparse/sparse_read.cpp b/libsparse/sparse_read.cpp index c4c182358..0f39172e2 100644 --- a/libsparse/sparse_read.cpp +++ b/libsparse/sparse_read.cpp @@ -58,14 +58,15 @@ static std::string ErrorString(int err) { class SparseFileSource { public: - /* Seeks the source ahead by the given offset. */ - virtual void Seek(int64_t offset) = 0; + /* Seeks the source ahead by the given offset. + * Return 0 if successful. */ + virtual int Seek(int64_t offset) = 0; /* Return the current offset. */ virtual int64_t GetOffset() = 0; - /* Set the current offset. Return 0 if successful. */ - virtual int SetOffset(int64_t offset) = 0; + /* Rewind to beginning. Return 0 if successful. */ + virtual int Rewind() = 0; /* Adds the given length from the current offset of the source to the file at the given block. * Return 0 if successful. */ @@ -88,12 +89,14 @@ class SparseFileFdSource : public SparseFileSource { SparseFileFdSource(int fd) : fd(fd) {} ~SparseFileFdSource() override {} - void Seek(int64_t off) override { lseek64(fd, off, SEEK_CUR); } + int Seek(int64_t off) override { + return lseek64(fd, off, SEEK_CUR) != -1 ? 0 : -errno; + } int64_t GetOffset() override { return lseek64(fd, 0, SEEK_CUR); } - int SetOffset(int64_t offset) override { - return lseek64(fd, offset, SEEK_SET) == offset ? 0 : -errno; + int Rewind() override { + return lseek64(fd, 0, SEEK_SET) == 0 ? 0 : -errno; } int AddToSparseFile(struct sparse_file* s, int64_t len, unsigned int block) override { @@ -120,39 +123,74 @@ class SparseFileFdSource : public SparseFileSource { class SparseFileBufSource : public SparseFileSource { private: + char* buf_start; + char* buf_end; char* buf; int64_t offset; + int AccessOkay(int64_t len) { + if (len <= 0) return -EINVAL; + if (buf < buf_start) return -EOVERFLOW; + if (buf >= buf_end) return -EOVERFLOW; + if (len > buf_end - buf) return -EOVERFLOW; + + return 0; + } + public: - SparseFileBufSource(char* buf) : buf(buf), offset(0) {} + SparseFileBufSource(char* buf, uint64_t len) { + this->buf = buf; + this->offset = 0; + this->buf_start = buf; + this->buf_end = buf + len; + } ~SparseFileBufSource() override {} - void Seek(int64_t off) override { + int Seek(int64_t off) override { + int ret = AccessOkay(off); + if (ret < 0) { + return ret; + } buf += off; offset += off; + return 0; } int64_t GetOffset() override { return offset; } - int SetOffset(int64_t off) override { - buf += off - offset; - offset = off; + int Rewind() override { + buf = buf_start; + offset = 0; return 0; } int AddToSparseFile(struct sparse_file* s, int64_t len, unsigned int block) override { + int ret = AccessOkay(len); + if (ret < 0) { + return ret; + } return sparse_file_add_data(s, buf, len, block); } int ReadValue(void* ptr, int len) override { + int ret = AccessOkay(len); + if (ret < 0) { + return ret; + } memcpy(ptr, buf, len); - Seek(len); + buf += len; + offset += len; return 0; } int GetCrc32(uint32_t* crc32, int64_t len) override { + int ret = AccessOkay(len); + if (ret < 0) { + return ret; + } *crc32 = sparse_crc32(*crc32, buf, len); - Seek(len); + buf += len; + offset += len; return 0; } }; @@ -175,7 +213,7 @@ static int process_raw_chunk(struct sparse_file* s, unsigned int chunk_size, SparseFileSource* source, unsigned int blocks, unsigned int block, uint32_t* crc32) { int ret; - int64_t len = blocks * s->block_size; + int64_t len = (int64_t)blocks * s->block_size; if (chunk_size % s->block_size != 0) { return -EINVAL; @@ -196,7 +234,10 @@ static int process_raw_chunk(struct sparse_file* s, unsigned int chunk_size, return ret; } } else { - source->Seek(len); + ret = source->Seek(len); + if (ret < 0) { + return ret; + } } return 0; @@ -379,7 +420,10 @@ static int sparse_file_read_sparse(struct sparse_file* s, SparseFileSource* sour /* Skip the remaining bytes in a header that is longer than * we expected. */ - source->Seek(sparse_header.file_hdr_sz - SPARSE_HEADER_LEN); + ret = source->Seek(sparse_header.file_hdr_sz - SPARSE_HEADER_LEN); + if (ret < 0) { + return ret; + } } for (i = 0; i < sparse_header.total_chunks; i++) { @@ -392,7 +436,10 @@ static int sparse_file_read_sparse(struct sparse_file* s, SparseFileSource* sour /* Skip the remaining bytes in a header that is longer than * we expected. */ - source->Seek(sparse_header.chunk_hdr_sz - CHUNK_HEADER_LEN); + ret = source->Seek(sparse_header.chunk_hdr_sz - CHUNK_HEADER_LEN); + if (ret < 0) { + return ret; + } } ret = process_chunk(s, source, sparse_header.chunk_hdr_sz, &chunk_header, cur_block, crc_ptr); @@ -474,11 +521,6 @@ int sparse_file_read(struct sparse_file* s, int fd, bool sparse, bool crc) { } } -int sparse_file_read_buf(struct sparse_file* s, char* buf, bool crc) { - SparseFileBufSource source(buf); - return sparse_file_read_sparse(s, &source, crc); -} - static struct sparse_file* sparse_file_import_source(SparseFileSource* source, bool verbose, bool crc) { int ret; @@ -510,6 +552,14 @@ static struct sparse_file* sparse_file_import_source(SparseFileSource* source, b return nullptr; } + if (!sparse_header.blk_sz || (sparse_header.blk_sz % 4)) { + return nullptr; + } + + if (!sparse_header.total_blks) { + return nullptr; + } + len = (int64_t)sparse_header.total_blks * sparse_header.blk_sz; s = sparse_file_new(sparse_header.blk_sz, len); if (!s) { @@ -517,7 +567,7 @@ static struct sparse_file* sparse_file_import_source(SparseFileSource* source, b return nullptr; } - ret = source->SetOffset(0); + ret = source->Rewind(); if (ret < 0) { verbose_error(verbose, ret, "seeking"); sparse_file_destroy(s); @@ -540,8 +590,8 @@ struct sparse_file* sparse_file_import(int fd, bool verbose, bool crc) { return sparse_file_import_source(&source, verbose, crc); } -struct sparse_file* sparse_file_import_buf(char* buf, bool verbose, bool crc) { - SparseFileBufSource source(buf); +struct sparse_file* sparse_file_import_buf(char* buf, size_t len, bool verbose, bool crc) { + SparseFileBufSource source(buf, len); return sparse_file_import_source(&source, verbose, crc); }