Merge "Add checking for sparse file format"
This commit is contained in:
commit
4915f04165
7 changed files with 188 additions and 60 deletions
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue