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
diff --git a/libsparse/Android.bp b/libsparse/Android.bp
index 3f9aeb2..02bfee6 100644
--- a/libsparse/Android.bp
+++ b/libsparse/Android.bp
@@ -96,12 +96,14 @@
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 2f75349..9f91269 100644
--- a/libsparse/include/sparse/sparse.h
+++ b/libsparse/include/sparse/sparse.h
@@ -244,21 +244,6 @@
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
*
* @fd - file descriptor to read from
@@ -277,6 +262,7 @@
* 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 @@
*
* 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 b2c5407..cb5d730 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 @@
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 @@
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 @@
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;
diff --git a/libsparse/sparse_fuzzer.cpp b/libsparse/sparse_fuzzer.cpp
index 42f331f..235d15d 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 c4c1823..0f39172 100644
--- a/libsparse/sparse_read.cpp
+++ b/libsparse/sparse_read.cpp
@@ -58,14 +58,15 @@
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 @@
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 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 @@
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 @@
return ret;
}
} else {
- source->Seek(len);
+ ret = source->Seek(len);
+ if (ret < 0) {
+ return ret;
+ }
}
return 0;
@@ -379,7 +420,10 @@
/* 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 @@
/* 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_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 @@
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 @@
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 @@
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);
}