[Libguestfs] [PATCH nbdkit 5/5] New filter: evil
Richard W.M. Jones
rjones at redhat.com
Tue May 16 16:24:58 UTC 2023
On Tue, May 16, 2023 at 10:55:25AM -0500, Eric Blake wrote:
> > +static int
> > +evil_config (nbdkit_next_config *next, nbdkit_backend *nxdata,
> > + const char *key, const char *value)
> > +{
> > + if (strcmp (key, "evil") == 0 || strcmp (key, "evil-mode") == 0) {
> > + if (strcmp (value, "cosmic-rays") == 0 ||
> > + strcmp (value, "cosmic") == 0) {
>
> Do we want strcasecmp on these? Do we want to allow _ as a synonym to -?
We're a bit random about this. There are a few places where we use
ascii_strcasecmp, but mostly we use strcmp ...
[...]
> > + /* Choose the block size based on the probability, so that at least
> > + * 100 bits are expected to be corrupted in the block. Block size
> > + * must be a power of 2.
> > + */
> > + block_size = next_power_of_2 ((uint64_t) (100. / evil_probability));
>
> ...and I'm worried that your block_size computation reaches a point
> where it does not do what we want if the probably gets too close to
> zero. Even if it is not division by zero, division by a subnormal
> float can easily produce overflow to infinity, which converts to
> UINT64_MAX, and next_power_of_2(UINT64_MAX) was untested in patch 3's
> unit tests, but appears like it will be '1 << (64 - 64)' == 1, which
> isn't really desirable.
Yes this is definitely a bug. I meant to avoid divide by zero, and
did in one place, but not everywhere.
I think it's worthwhile allowing the probability to be zero, not least
because it avoids a special case for the user. However I will need to
add a test to make sure I've avoided all the problems.
Also as you say very small but non-zero probabilities can make
block_size blow up.
Let me fix all of that & add tests.
> > +
> > + uint64_t offs, intvl, i, rand;
> > + const uint64_t dinvp = (uint64_t) (2.0 * (1.0 / evil_probability));
>
> [1] Ah, I see - you did mean multiplication, not exponentiation. Like
> you commented elsewhere, it is not quite a true uniform distribution,
> but certainly seems sane enough for the intent.
I will clarify the comment.
> > +++ b/tests/test-evil-cosmic.sh
>
> > +
> > +f="test-evil-cosmic.out"
> > +rm -f $f
> > +cleanup_fn rm -f $f
> > +
> > +# 80 million zero bits in the backing disk, and the filter will
> > +# randomly flip (ie. set high) 1 in 800,000 bits, or about 100.
> > +
> > +# XXX Actually the number of set bits clusters around 80. There could
> > +# be a mistake in my calculations or the interval algorithm we use
> > +# might be biased.
>
> I have not closely inspected the math yet to see if there's an obvious
> bias (treat this as a first-pass quick review looking for obvious
> code/shell bugs, as opposed to more insidious deep analysis bugs).
> But that comment does mean we probably ought to double-check things
> prior to v2.
Interestingly the other tests are nicely distributed around the
expected values, it's just this one test where things are out of
whack. I'll add a bit of debugging to the interval calculation etc to
see if I can see what's going on.
> > +export f
> > +nbdkit -U - null 10000000 \
> > + --filter=evil --filter=noextents \
> > + evil=cosmic-rays evil-probability=1/800000 \
> > + --run 'nbdcopy "$uri" $f'
> > +
> > +# This will give an approximate count of the number of set bits.
> > +
> > +zbytes="$( od -A n -w1 -v -t x1 < $f | sort | uniq -c |
> > + $SED -n -E -e 's/([0-9]+)[[:space:]]+00[[:space:]]*$/\1/p' )"
> > +nzbits=$(( 10000000 - zbytes )); # looks wrong but actually correct ...
>
> Interesting computation!
I can probably change this to use Python code. Because I wanted to
use nbdcopy (to allow the filter to be hit by multiple threads), I
wasn't using nbdsh, so Python wasn't immediately available ...
> > +# Expect about 50 stuck-high bits.
> > +buf = h.pread(10000000, 0)
> > +bits = count_bits(buf)
> > +print("stuck high bits: %d (expected 50)" % bits)
> > +assert(bits > 20 and bits < 80)
> > +
> > +# If we read subsets they should match the contents of the buffer.
> > +buf1 = h.pread(1000, 1000)
> > +assert(buf1 == buf[1000:2000])
> > +
> > +buf1 = h.pread(10000, 999)
> > +assert(buf1 == buf[999:10999])
>
> Should we also assert that there is at least one stuck bit in those
> two ranges, and/or pick a different or larger range to improve the
> chances of that being the case?
So I think you mean pick a range where we know there is a stuck bit
before doing the read + assert. I will see. With a 1:800,000 chance,
it's not likely the ranges as written would have stuck bits so we're
comparing zeroes ...
> > +++ b/tests/test-evil-stuck-wires.sh
>
> > +
> > +# Run nbdkit with the evil filter.
> > +start_nbdkit -P $pidfile -U $sock \
> > + --filter=evil --filter=noextents \
> > + null 1G evil=stuck-wires evil-probability=1/10000
> > +
> > +# Reads from the filter should have 1:10,000 bits stuck high or low.
> > +# However we don't see stuck low bits are we are always reading
>
> s/are we are/since we are/
Will fix.
> Overall looks like an interesting idea for a filter.
Thanks,
Rich.
--
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
nbdkit - Flexible, fast NBD server with plugins
https://gitlab.com/nbdkit/nbdkit
More information about the Libguestfs
mailing list