extents: Cap maximum reply length
commit6e0dc839ea5f17d7b1c2f97cfd96461b62a8377c
authorEric Blake <eblake@redhat.com>
Mon, 17 Jun 2019 19:52:46 +0000 (17 14:52 -0500)
committerEric Blake <eblake@redhat.com>
Mon, 17 Jun 2019 20:06:37 +0000 (17 15:06 -0500)
treec652710c424895a3719a3f4bd0f72f2ef7987dcb
parent16d0a73c449b57d8a9d3dcf6a855155d3acb9fc3
extents: Cap maximum reply length

A devious plugin can cause an 8-fold increase in the reply size in
relation to the request size, by alternating status every byte of the
request. In the worst case, this can cause the client to reject our
response and kill the connection because the response is too large.
What's more, it consumes a lot of memory to track that many extents.
Let's put an upper bound on the maximum number of extents we are
willing to return (1M extents is an 8M reply chunk).

Pre-patch, this can be used to demonstrate the problem:

$ nbdkit -f sh - <<\EOF
#!/bin/bash
size=$((9*1024*1024))
case $1 in
  get_size) echo $size ;;
  pread) dd iflag=skip_bytes,count_bytes skip=$4 count=$3 if=/dev/zero || exit 1 ;;
  can_extents) ;;
  extents)
    # Unrealistic in real life, but works to provoke the bug. For a full 9M
    # query, this produces ~100M for nbdkit to parse, and in turn tries to
    # produce a 72M reply chunk if we don't cap extents.
    for ((i=$4; i<$4+$3; i++)); do
      echo $i 1 $((i&1))
    done ;;
  *) exit 2 ;;
esac
EOF

$ ~/libnbd/run sh/nbdsh
...
nbd> h.connect_tcp("localhost","10809")
nbd> def f(data,metacontext,offset,e):
...   print ("entries:%d" % len(e))
...
nbd> h.block_status(9*1024*1024,0,0,f)
Traceback (most recent call last):
  File "/usr/lib64/python3.7/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<console>", line 1, in <module>
  File "/home/eblake/libnbd/python/nbd.py", line 577, in block_status
    return libnbdmod.block_status (self._o, count, offset, data, extent, flags)
RuntimeError: nbd_block_status: invalid server reply length

Signed-off-by: Eric Blake <eblake@redhat.com>
server/extents.c