From: Nir Soffer <nirsof@gmail.com>
To: qemu-devel@nongnu.org
Cc: Eric Blake <eblake@redhat.com>,
qemu-block@nongnu.org, Markus Armbruster <armbru@redhat.com>,
Kevin Wolf <kwolf@redhat.com>, Fam Zheng <fam@euphon.net>,
Hanna Reitz <hreitz@redhat.com>, Nir Soffer <nirsof@gmail.com>
Subject: [PATCH v2 2/2] block/null: Add read-pattern option
Date: Wed, 30 Apr 2025 23:37:17 +0300 [thread overview]
Message-ID: <20250430203717.16359-3-nirsof@gmail.com> (raw)
In-Reply-To: <20250430203717.16359-1-nirsof@gmail.com>
When the `read-zeroes` is set, reads produce zeroes, and block status
return BDRV_BLOCK_ZERO, emulating a sparse image.
If we don't set `read-zeros` we report BDRV_BLOCK_DATA, but image data
is undefined; posix_memalign, _aligned_malloc, valloc, or memalign do
not promise to zero allocated memory.
When computing a blkhash of an image via qemu-nbd, we want to test 3
cases:
1. Sparse image: skip reading the entire image based on block status
result, and use a pre-computed zero block hash.
2. Image full of zeroes: read the entire image, detect block full of
zeroes and skip block hash computation.
3. Image full of data: read the entire image and compute a hash of all
blocks.
This change adds `read-pattern` option. If the option is set, reads
produce the specified pattern. With this option we can emulate an image
full of zeroes or full of non-zeroes.
The following examples shows how the new option can be used with blksum
(or nbdcopy --blkhash) to compute a blkhash of an image using the
null-co driver.
Sparse image - the very fast path:
% ./qemu-nbd -r -t -e 0 -f raw -k /tmp/sparse.sock \
"json:{'driver': 'raw', 'file': {'driver': 'null-co', 'size': '100g', 'read-zeroes': true}}" &
% time blksum 'nbd+unix:///?socket=/tmp/sparse.sock'
300ad1efddb063822fea65ae3174cd35320939d4d0b050613628c6e1e876f8f6 nbd+unix:///?socket=/tmp/sparse.sock
blksum 'nbd+unix:///?socket=/tmp/sparse.sock' 0.05s user 0.01s system 92% cpu 0.061 total
Image full of zeros - same hash, 268 times slower:
% ./qemu-nbd -r -t -e 0 -f raw -k /tmp/zero.sock \
"json:{'driver': 'raw', 'file': {'driver': 'null-co', 'size': '100g', 'read-pattern': 0}}" &
% time blksum 'nbd+unix:///?socket=/tmp/zero.sock'
300ad1efddb063822fea65ae3174cd35320939d4d0b050613628c6e1e876f8f6 nbd+unix:///?socket=/tmp/zero.sock
blksum 'nbd+unix:///?socket=/tmp/zero.sock' 7.45s user 22.57s system 183% cpu 16.347 total
Image full of data - difference hash, heavy cpu usage:
% ./qemu-nbd -r -t -e 0 -f raw -k /tmp/data.sock \
"json:{'driver': 'raw', 'file': {'driver': 'null-co', 'size': '100g', 'read-pattern': 255}}" &
% time blksum 'nbd+unix:///?socket=/tmp/data.sock'
2c122b3ed28c83ede3c08485659fa9b56ee54ba1751db74d8ba9aa13d9866432 nbd+unix:///?socket=/tmp/data.sock
blksum 'nbd+unix:///?socket=/tmp/data.sock' 46.05s user 14.15s system 448% cpu 13.414 total
Specifying both `read-zeroes` and `read-pattern` is an error since
`read-zeroes` implies a sparse image. Example errors:
% ./qemu-img map --output json \
"json:{'driver': 'raw', 'file': {'driver': 'null-co', 'read-pattern': -1}}"
qemu-img: Could not open 'json:{...}': read_pattern is out of range (0-255)
% ./qemu-img map --output json \
"json:{'driver': 'raw', 'file': {'driver': 'null-co', 'read-pattern': 0, 'read-zeroes': true}}"
qemu-img: Could not open 'json:{...}': The parameters read-zeroes and read-pattern are in conflict
Tested on top of
https://lists.gnu.org/archive/html/qemu-devel/2025-04/msg05096.html.
Signed-off-by: Nir Soffer <nirsof@gmail.com>
---
block/null.c | 34 +++++++++++++++++++++++++-
docs/devel/secure-coding-practices.rst | 3 ++-
qapi/block-core.json | 14 +++++++++--
3 files changed, 47 insertions(+), 4 deletions(-)
diff --git a/block/null.c b/block/null.c
index 7ba87bd9a9..62c1da2b07 100644
--- a/block/null.c
+++ b/block/null.c
@@ -22,11 +22,14 @@
#define NULL_OPT_LATENCY "latency-ns"
#define NULL_OPT_ZEROES "read-zeroes"
+#define NULL_OPT_PATTERN "read-pattern"
typedef struct {
int64_t length;
int64_t latency_ns;
bool read_zeroes;
+ bool has_read_pattern;
+ int read_pattern;
} BDRVNullState;
static QemuOptsList runtime_opts = {
@@ -49,6 +52,11 @@ static QemuOptsList runtime_opts = {
.type = QEMU_OPT_BOOL,
.help = "return zeroes when read",
},
+ {
+ .name = NULL_OPT_PATTERN,
+ .type = QEMU_OPT_NUMBER,
+ .help = "return pattern when read",
+ },
{ /* end of list */ }
},
};
@@ -85,6 +93,7 @@ static int null_open(BlockDriverState *bs, QDict *options, int flags,
int ret = 0;
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
+
qemu_opts_absorb_qdict(opts, options, &error_abort);
s->length =
qemu_opt_get_size(opts, BLOCK_OPT_SIZE, 1 << 30);
@@ -93,10 +102,28 @@ static int null_open(BlockDriverState *bs, QDict *options, int flags,
if (s->latency_ns < 0) {
error_setg(errp, "latency-ns is invalid");
ret = -EINVAL;
+ goto out;
}
s->read_zeroes = qemu_opt_get_bool(opts, NULL_OPT_ZEROES, false);
- qemu_opts_del(opts);
+ s->has_read_pattern = qemu_opt_find(opts, NULL_OPT_PATTERN) != NULL;
+ if (s->has_read_pattern) {
+ if (s->read_zeroes) {
+ error_setg(errp, "The parameters read-zeroes and read-pattern "
+ "are in conflict");
+ ret = -EINVAL;
+ goto out;
+ }
+ s->read_pattern = qemu_opt_get_number(opts, NULL_OPT_PATTERN, 0);
+ if (s->read_pattern < 0 || s->read_pattern > UINT8_MAX) {
+ error_setg(errp, "read_pattern is out of range (0-%d)", UINT8_MAX);
+ ret = -EINVAL;
+ goto out;
+ }
+ }
bs->supported_write_flags = BDRV_REQ_FUA;
+
+out:
+ qemu_opts_del(opts);
return ret;
}
@@ -125,6 +152,8 @@ static coroutine_fn int null_co_preadv(BlockDriverState *bs,
if (s->read_zeroes) {
qemu_iovec_memset(qiov, 0, 0, bytes);
+ } else if (s->has_read_pattern) {
+ qemu_iovec_memset(qiov, 0, s->read_pattern, bytes);
}
return null_co_common(bs);
@@ -199,6 +228,8 @@ static BlockAIOCB *null_aio_preadv(BlockDriverState *bs,
if (s->read_zeroes) {
qemu_iovec_memset(qiov, 0, 0, bytes);
+ } else if (s->has_read_pattern) {
+ qemu_iovec_memset(qiov, 0, s->read_pattern, bytes);
}
return null_aio_common(bs, cb, opaque);
@@ -272,6 +303,7 @@ null_co_get_allocated_file_size(BlockDriverState *bs)
static const char *const null_strong_runtime_opts[] = {
BLOCK_OPT_SIZE,
NULL_OPT_ZEROES,
+ NULL_OPT_PATTERN,
NULL
};
diff --git a/docs/devel/secure-coding-practices.rst b/docs/devel/secure-coding-practices.rst
index 0454cc527e..73830684ea 100644
--- a/docs/devel/secure-coding-practices.rst
+++ b/docs/devel/secure-coding-practices.rst
@@ -111,5 +111,6 @@ Use of null-co block drivers
The ``null-co`` block driver is designed for performance: its read accesses are
not initialized by default. In case this driver has to be used for security
research, it must be used with the ``read-zeroes=on`` option which fills read
-buffers with zeroes. Security issues reported with the default
+buffers with zeroes, or with the ``read-pattern=N`` option which fills read
+buffers with pattern. Security issues reported with the default
(``read-zeroes=off``) will be discarded.
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 7c95c9e36a..2205ac9758 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -3295,13 +3295,23 @@
#
# @read-zeroes: if true, emulate a sparse image, and reads from the
# device produce zeroes; if false, emulate an allocated image but
-# reads from the device leave the buffer unchanged.
+# reads from the device leave the buffer unchanged. Mutually
+# exclusive with @read-pattern.
# (default: false; since: 4.1)
#
+# @read-pattern: if set, emulate an allocated image, and reads from the
+# device produce the specified byte value; if unset, reads from the
+# device leave the buffer unchanged. Mutually exclusive with
+# @read-zeroes.
+# (since: 10.1)
+#
# Since: 2.9
##
{ 'struct': 'BlockdevOptionsNull',
- 'data': { '*size': 'int', '*latency-ns': 'uint64', '*read-zeroes': 'bool' } }
+ 'data': { '*size': 'int',
+ '*latency-ns': 'uint64',
+ '*read-zeroes': 'bool',
+ '*read-pattern': 'uint8' } }
##
# @BlockdevOptionsNVMe:
--
2.39.5 (Apple Git-154)
next prev parent reply other threads:[~2025-04-30 20:38 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-04-30 20:37 [PATCH v2 0/2] block/null: Add read-pattern option Nir Soffer
2025-04-30 20:37 ` [PATCH v2 1/2] block/null: Report DATA if not reading zeroes Nir Soffer
2025-05-08 4:37 ` Markus Armbruster
2025-05-08 5:03 ` Markus Armbruster
2025-05-08 5:20 ` Markus Armbruster
2025-05-16 21:35 ` Nir Soffer
2025-05-16 21:32 ` Nir Soffer
2025-05-16 21:31 ` Nir Soffer
2025-04-30 20:37 ` Nir Soffer [this message]
2025-05-08 5:28 ` [PATCH v2 2/2] block/null: Add read-pattern option Markus Armbruster
2025-05-08 14:30 ` Eric Blake
2025-05-09 7:19 ` Markus Armbruster
2025-05-16 21:12 ` Nir Soffer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250430203717.16359-3-nirsof@gmail.com \
--to=nirsof@gmail.com \
--cc=armbru@redhat.com \
--cc=eblake@redhat.com \
--cc=fam@euphon.net \
--cc=hreitz@redhat.com \
--cc=kwolf@redhat.com \
--cc=qemu-block@nongnu.org \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).