[PATCH] dd: fix corrupted/incomplete reads/skips.

Ralf Friedl Ralf.Friedl at online.de
Thu Nov 9 18:54:03 UTC 2017


Nicholas Clark wrote:
> Busybox dd expects that read() calls (as provided by safe_read) will
> always return the requested amount of data unless at EOF. This isn't
> true for safe_read(), but it is true for full_read() (which loops
> over safe_read until enough data has been retrieved).
>
> This patch replaces dd's safe_read() calls with full_read() to make
> sure that the underlying assumptions about read-length always hold
> true.
>
> Without this patch, users on some platforms can get corrupted reads
> or incomplete skips.
>
> Signed-off-by: Nicholas Clark <nicholas.clark at gmail.com 
> <http://gmail.com>>
> ---
>  coreutils/dd.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/coreutils/dd.c b/coreutils/dd.c
> index d302f35d3..6fc062d8d 100644
> --- a/coreutils/dd.c
> +++ b/coreutils/dd.c
> @@ -450,7 +450,7 @@ int dd_main(int argc UNUSED_PARAM, char **argv)
> size_t blocksz = (G.flags & FLAG_SKIP_BYTES) ? 1 : ibs;
> if (lseek(ifd, skip * blocksz, SEEK_CUR) < 0) {
> do {
> -ssize_t n = safe_read(ifd, ibuf, blocksz);
> +ssize_t n = full_read(ifd, ibuf, blocksz);
> if (n < 0)
> goto die_infile;
> if (n == 0)
> @@ -466,7 +466,7 @@ int dd_main(int argc UNUSED_PARAM, char **argv)
> while (!(G.flags & FLAG_COUNT) || (G.in_full + G.in_part != count)) {
> ssize_t n;
> -n = safe_read(ifd, ibuf, ibs);
> +n = full_read(ifd, ibuf, ibs);
> if (n == 0)
> break;
> if (n < 0) {
>
If we take GNU dd as a reference, then dd doesn't expect read() calls to 
return the requested amount of data. The very fact that the message at 
the end says "(full)+(partial) records in/out" shows that partial reads 
are expected to happen. With yout patch, there can never be a partial 
read, except maybe on the last record.

A look at 
http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dd.html shows 
that even safe_read is wrong: "For SIGINT, the /dd/ utility shall 
interrupt its current processing, write status information to standard 
error, and exit as though terminated by SIGINT. It shall take the 
standard action for all other signals"
The standard also states: "1. An input block is read. 2. If the input 
block is shorter ...", nothing about forcing the block to the full size.

Your patch changes both the handling of the skip option and the regular 
read. GNU dd uses simple read in both cases, unless the nonstandard 
iflag=fullblock is used. So the proper fix would be to use read as 
default and full_read if iflag=fullblock is specified. Busybox dd 
already has conditional support for iflag, but only for skip_bytes, not 
for fullblock. It should take just a few bytes to add fullblock.

A variation of the test by Denys shows how GNU dd behaves with short 
reads in the skip and data phase:

Without fullblock, output is "34":
$ for i in $(seq 9); do sleep 1; echo -n $i; done | strace -s99 dd bs=2 
skip=2 count=2
read(0, "1", 2)                         = 1
read(0, "2", 2)                         = 1
write(2, "dd: ", 4)                     = 4
write(2, "warning: partial read (1 byte); suggest iflag=fullblock", 55) = 55
write(2, "\n", 1)                       = 1
write(2, "dd: ", 4)                     = 4
write(2, "'standard input': cannot skip to specified offset", 49) = 49
write(2, "\n", 1)                       = 1
read(0, "3", 2)                         = 1
write(1, "3", 13)                        = 1
read(0, "4", 2)                         = 1
write(1, "4", 14)                        = 1
close(0)                                = 0
close(1)                                = 0
write(2, "0+2 records in\n0+2 records out\n", 31) = 31

With fullblock, output is "5678":
read(0, "1", 2)                         = 1
read(0, "2", 1)                         = 1
read(0, "3", 2)                         = 1
read(0, "4", 1)                         = 1
read(0, "5", 2)                         = 1
read(0, "6", 1)                         = 1
write(1, "56", 256)                       = 2
read(0, "7", 2)                         = 1
read(0, "8", 1)                         = 1
write(1, "78", 278)                       = 2
close(0)                                = 0
close(1)                                = 0
write(2, "2+0 records in\n2+0 records out\n", 31) = 31



More information about the busybox mailing list