Merge pull request #1194 from robertovargas-arm/io-fix

io: block: fix block_read/write may read/write overlap buffer
This commit is contained in:
davidcunado-arm 2017-12-19 17:39:20 +00:00 committed by GitHub
commit dced20e17b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 262 additions and 142 deletions

View File

@ -167,15 +167,98 @@ static int block_seek(io_entity_t *entity, int mode, ssize_t offset)
return 0;
}
/*
* This function allows the caller to read any number of bytes
* from any position. It hides from the caller that the low level
* driver only can read aligned blocks of data. For this reason
* we need to handle the use case where the first byte to be read is not
* aligned to start of the block, the last byte to be read is also not
* aligned to the end of a block, and there are zero or more blocks-worth
* of data in between.
*
* In such a case we need to read more bytes than requested (i.e. full
* blocks) and strip-out the leading bytes (aka skip) and the trailing
* bytes (aka padding). See diagram below
*
* cur->file_pos ------------
* |
* cur->base |
* | |
* v v<---- length ---->
* --------------------------------------------------------------
* | | block#1 | | block#n |
* | block#0 | + | ... | + |
* | | <- skip -> + | | + <- padding ->|
* ------------------------+----------------------+--------------
* ^ ^
* | |
* v iteration#1 iteration#n v
* --------------------------------------------------
* | | | |
* |<---- request ---->| ... |<----- request ---->|
* | | | |
* --------------------------------------------------
* / / | |
* / / | |
* / / | |
* / / | |
* / / | |
* / / | |
* / / | |
* / / | |
* / / | |
* / / | |
* <---- request ------> <------ request ----->
* --------------------- -----------------------
* | | | | | |
* |<-skip->|<-nbytes->| -------->|<-nbytes->|<-padding->|
* | | | | | | |
* --------------------- | -----------------------
* ^ \ \ | | |
* | \ \ | | |
* | \ \ | | |
* buf->offset \ \ buf->offset | |
* \ \ | |
* \ \ | |
* \ \ | |
* \ \ | |
* \ \ | |
* \ \ | |
* \ \ | |
* --------------------------------
* | | | |
* buffer-------------->| | ... | |
* | | | |
* --------------------------------
* <-count#1->| |
* <---------- count#n -------->
* <---------- length ---------->
*
* Additionally, the IO driver has an underlying buffer that is at least
* one block-size and may be big enough to allow.
*/
static int block_read(io_entity_t *entity, uintptr_t buffer, size_t length,
size_t *length_read)
{
block_dev_state_t *cur;
io_block_spec_t *buf;
io_block_ops_t *ops;
size_t aligned_length, skip, count, left, padding, block_size;
int lba;
int buffer_not_aligned;
size_t block_size, left;
size_t nbytes; /* number of bytes read in one iteration */
size_t request; /* number of requested bytes in one iteration */
size_t count; /* number of bytes already read */
/*
* number of leading bytes from start of the block
* to the first byte to be read
*/
size_t skip;
/*
* number of trailing bytes between the last byte
* to be read and the end of the block
*/
size_t padding;
assert(entity->info != (uintptr_t)NULL);
cur = (block_dev_state_t *)entity->info;
@ -186,102 +269,107 @@ static int block_read(io_entity_t *entity, uintptr_t buffer, size_t length,
(length > 0) &&
(ops->read != 0));
if ((buffer & (block_size - 1)) != 0) {
/*
* We don't know the number of bytes that we are going
* to read in every iteration, because it will depend
* on the low level driver.
*/
count = 0;
for (left = length; left > 0; left -= nbytes) {
/*
* buffer isn't aligned with block size.
* Block device always relies on DMA operation.
* It's better to make the buffer as block size aligned.
* We must only request operations aligned to the block
* size. Therefore if file_pos is not block-aligned,
* we have to request the operation to start at the
* previous block boundary and skip the leading bytes. And
* similarly, the number of bytes requested must be a
* block size multiple
*/
buffer_not_aligned = 1;
} else {
buffer_not_aligned = 0;
}
skip = cur->file_pos & (block_size - 1);
skip = cur->file_pos % block_size;
aligned_length = ((skip + length) + (block_size - 1)) &
~(block_size - 1);
padding = aligned_length - (skip + length);
left = aligned_length;
do {
/*
* Calculate the block number containing file_pos
* - e.g. block 3.
*/
lba = (cur->file_pos + cur->base) / block_size;
if (left >= buf->length) {
if (skip + left > buf->length) {
/*
* Since left is larger, it's impossible to padding.
*
* If buffer isn't aligned, we need to use aligned
* buffer instead.
* The underlying read buffer is too small to
* read all the required data - limit to just
* fill the buffer, and then read again.
*/
if (skip || buffer_not_aligned) {
/*
* The beginning address (file_pos) isn't
* aligned with block size, we need to use
* block buffer to read block. Since block
* device is always relied on DMA operation.
*/
count = ops->read(lba, buf->offset,
buf->length);
} else {
count = ops->read(lba, buffer, buf->length);
}
assert(count == buf->length);
cur->file_pos += count - skip;
if (skip || buffer_not_aligned) {
/*
* Since there's not aligned block size caused
* by skip or not aligned buffer, block buffer
* is used to store data.
*/
memcpy((void *)buffer,
(void *)(buf->offset + skip),
count - skip);
}
left = left - (count - skip);
request = buf->length;
} else {
if (skip || padding || buffer_not_aligned) {
/*
* The beginning address (file_pos) isn't
* aligned with block size, we have to read
* full block by block buffer instead.
* The size isn't aligned with block size.
* Use block buffer to avoid overflow.
*
* If buffer isn't aligned, use block buffer
* to avoid DMA error.
*/
count = ops->read(lba, buf->offset, left);
} else
count = ops->read(lba, buffer, left);
assert(count == left);
left = left - (skip + padding);
cur->file_pos += left;
if (skip || padding || buffer_not_aligned) {
/*
* Since there's not aligned block size or
* buffer, block buffer is used to store data.
*/
memcpy((void *)buffer,
(void *)(buf->offset + skip),
left);
}
/* It's already the last block operation */
left = 0;
/*
* The underlying read buffer is big enough to
* read all the required data. Calculate the
* number of bytes to read to align with the
* block size.
*/
request = skip + left;
request = (request + (block_size - 1)) & ~(block_size - 1);
}
skip = cur->file_pos % block_size;
} while (left > 0);
*length_read = length;
request = ops->read(lba, buf->offset, request);
if (request <= skip) {
/*
* We couldn't read enough bytes to jump over
* the skip bytes, so we should have to read
* again the same block, thus generating
* the same error.
*/
return -EIO;
}
/*
* Need to remove skip and padding bytes,if any, from
* the read data when copying to the user buffer.
*/
nbytes = request - skip;
padding = (nbytes > left) ? nbytes - left : 0;
nbytes -= padding;
memcpy((void *)(buffer + count),
(void *)(buf->offset + skip),
nbytes);
cur->file_pos += nbytes;
count += nbytes;
}
assert(count == length);
*length_read = count;
return 0;
}
/*
* This function allows the caller to write any number of bytes
* from any position. It hides from the caller that the low level
* driver only can write aligned blocks of data.
* See comments for block_read for more details.
*/
static int block_write(io_entity_t *entity, const uintptr_t buffer,
size_t length, size_t *length_written)
{
block_dev_state_t *cur;
io_block_spec_t *buf;
io_block_ops_t *ops;
size_t aligned_length, skip, count, left, padding, block_size;
int lba;
int buffer_not_aligned;
size_t block_size, left;
size_t nbytes; /* number of bytes read in one iteration */
size_t request; /* number of requested bytes in one iteration */
size_t count; /* number of bytes already read */
/*
* number of leading bytes from start of the block
* to the first byte to be read
*/
size_t skip;
/*
* number of trailing bytes between the last byte
* to be read and the end of the block
*/
size_t padding;
assert(entity->info != (uintptr_t)NULL);
cur = (block_dev_state_t *)entity->info;
@ -293,75 +381,107 @@ static int block_write(io_entity_t *entity, const uintptr_t buffer,
(ops->read != 0) &&
(ops->write != 0));
if ((buffer & (block_size - 1)) != 0) {
/*
* We don't know the number of bytes that we are going
* to write in every iteration, because it will depend
* on the low level driver.
*/
count = 0;
for (left = length; left > 0; left -= nbytes) {
/*
* buffer isn't aligned with block size.
* Block device always relies on DMA operation.
* It's better to make the buffer as block size aligned.
* We must only request operations aligned to the block
* size. Therefore if file_pos is not block-aligned,
* we have to request the operation to start at the
* previous block boundary and skip the leading bytes. And
* similarly, the number of bytes requested must be a
* block size multiple
*/
buffer_not_aligned = 1;
} else {
buffer_not_aligned = 0;
}
skip = cur->file_pos & (block_size - 1);
skip = cur->file_pos % block_size;
aligned_length = ((skip + length) + (block_size - 1)) &
~(block_size - 1);
padding = aligned_length - (skip + length);
left = aligned_length;
do {
/*
* Calculate the block number containing file_pos
* - e.g. block 3.
*/
lba = (cur->file_pos + cur->base) / block_size;
if (left >= buf->length) {
/* Since left is larger, it's impossible to padding. */
if (skip || buffer_not_aligned) {
/*
* The beginning address (file_pos) isn't
* aligned with block size or buffer isn't
* aligned, we need to use block buffer to
* write block.
*/
count = ops->read(lba, buf->offset,
buf->length);
assert(count == buf->length);
memcpy((void *)(buf->offset + skip),
(void *)buffer,
count - skip);
count = ops->write(lba, buf->offset,
buf->length);
} else
count = ops->write(lba, buffer, buf->length);
assert(count == buf->length);
cur->file_pos += count - skip;
left = left - (count - skip);
if (skip + left > buf->length) {
/*
* The underlying read buffer is too small to
* read all the required data - limit to just
* fill the buffer, and then read again.
*/
request = buf->length;
} else {
if (skip || padding || buffer_not_aligned) {
/*
* The beginning address (file_pos) isn't
* aligned with block size, we need to avoid
* poluate data in the beginning. Reading and
* skipping the beginning is the only way.
* The size isn't aligned with block size.
* Use block buffer to avoid overflow.
*
* If buffer isn't aligned, use block buffer
* to avoid DMA error.
*/
count = ops->read(lba, buf->offset, left);
assert(count == left);
memcpy((void *)(buf->offset + skip),
(void *)buffer,
left - skip - padding);
count = ops->write(lba, buf->offset, left);
} else
count = ops->write(lba, buffer, left);
assert(count == left);
cur->file_pos += left - (skip + padding);
/* It's already the last block operation */
left = 0;
/*
* The underlying read buffer is big enough to
* read all the required data. Calculate the
* number of bytes to read to align with the
* block size.
*/
request = skip + left;
request = (request + (block_size - 1)) & ~(block_size - 1);
}
skip = cur->file_pos % block_size;
} while (left > 0);
*length_written = length;
/*
* The number of bytes that we are going to write
* from the user buffer will depend of the size
* of the current request.
*/
nbytes = request - skip;
padding = (nbytes > left) ? nbytes - left : 0;
nbytes -= padding;
/*
* If we have skip or padding bytes then we have to preserve
* some content and it means that we have to read before
* writing
*/
if (skip > 0 || padding > 0) {
request = ops->read(lba, buf->offset, request);
/*
* The read may return size less than
* requested. Round down to the nearest block
* boundary
*/
request &= ~(block_size-1);
if (request <= skip) {
/*
* We couldn't read enough bytes to jump over
* the skip bytes, so we should have to read
* again the same block, thus generating
* the same error.
*/
return -EIO;
}
nbytes = request - skip;
padding = (nbytes > left) ? nbytes - left : 0;
nbytes -= padding;
}
memcpy((void *)(buf->offset + skip),
(void *)(buffer + count),
nbytes);
request = ops->write(lba, buf->offset, request);
if (request <= skip)
return -EIO;
/*
* And the previous write operation may modify the size
* of the request, so again, we have to calculate the
* number of bytes that we consumed from the user
* buffer
*/
nbytes = request - skip;
padding = (nbytes > left) ? nbytes - left : 0;
nbytes -= padding;
cur->file_pos += nbytes;
count += nbytes;
}
assert(count == length);
*length_written = count;
return 0;
}