btrfs: test if rename handles dir item collision correctly
authorethanwu <ethanwu@synology.com>
Tue, 15 Dec 2020 03:59:06 +0000 (11:59 +0800)
committerEryu Guan <guaneryu@gmail.com>
Sun, 20 Dec 2020 16:18:43 +0000 (00:18 +0800)
This is a regression test for the issue fixed by the kernel commit titled
"btrfs: correctly calculate item size used when item key collision happens"

In this case, we'll simply rename many forged filename that cause collision
under a directory to see if rename failed and filesystem is forced readonly.

Signed-off-by: ethanwu <ethanwu@synology.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Eryu Guan <guaneryu@gmail.com>
src/btrfs_crc32c_forged_name.py [new file with mode: 0755]
tests/btrfs/154 [new file with mode: 0755]
tests/btrfs/154.out [new file with mode: 0644]
tests/btrfs/group

diff --git a/src/btrfs_crc32c_forged_name.py b/src/btrfs_crc32c_forged_name.py
new file mode 100755 (executable)
index 0000000..6c08fcb
--- /dev/null
@@ -0,0 +1,91 @@
+# SPDX-License-Identifier: GPL-2.0
+
+import struct
+import sys
+import os
+import argparse
+
+class CRC32(object):
+  """A class to calculate and manipulate CRC32."""
+  def __init__(self):
+    self.polynom = 0x82F63B78
+    self.table, self.reverse = [0]*256, [0]*256
+    self._build_tables()
+
+  def _build_tables(self):
+    for i in range(256):
+      fwd = i
+      rev = i << 24
+      for j in range(8, 0, -1):
+        # build normal table
+        if (fwd & 1) == 1:
+          fwd = (fwd >> 1) ^ self.polynom
+        else:
+          fwd >>= 1
+        self.table[i] = fwd & 0xffffffff
+        # build reverse table =)
+        if rev & 0x80000000 == 0x80000000:
+          rev = ((rev ^ self.polynom) << 1) | 1
+        else:
+          rev <<= 1
+        rev &= 0xffffffff
+        self.reverse[i] = rev
+
+  def calc(self, s):
+    """Calculate crc32 of a string.
+       Same crc32 as in (binascii.crc32)&0xffffffff.
+    """
+    crc = 0xffffffff
+    for c in s:
+      crc = (crc >> 8) ^ self.table[(crc ^ ord(c)) & 0xff]
+    return crc^0xffffffff
+
+  def forge(self, wanted_crc, s, pos=None):
+    """Forge crc32 of a string by adding 4 bytes at position pos."""
+    if pos is None:
+      pos = len(s)
+
+    # forward calculation of CRC up to pos, sets current forward CRC state
+    fwd_crc = 0xffffffff
+    for c in s[:pos]:
+      fwd_crc = (fwd_crc >> 8) ^ self.table[(fwd_crc ^ ord(c)) & 0xff]
+
+    # backward calculation of CRC up to pos, sets wanted backward CRC state
+    bkd_crc = wanted_crc^0xffffffff
+    for c in s[pos:][::-1]:
+      bkd_crc = ((bkd_crc << 8) & 0xffffffff) ^ self.reverse[bkd_crc >> 24]
+      bkd_crc ^= ord(c)
+
+    # deduce the 4 bytes we need to insert
+    for c in struct.pack('<L',fwd_crc)[::-1]:
+      bkd_crc = ((bkd_crc << 8) & 0xffffffff) ^ self.reverse[bkd_crc >> 24]
+      bkd_crc ^= ord(c)
+
+    res = s[:pos] + struct.pack('<L', bkd_crc) + s[pos:]
+    return res
+
+  def parse_args(self):
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-d", default=os.getcwd(), dest='dir',
+                        help="directory to generate forged names")
+    parser.add_argument("-c", default=1, type=int, dest='count',
+                        help="number of forged names to create")
+    return parser.parse_args()
+
+if __name__=='__main__':
+
+  crc = CRC32()
+  wanted_crc = 0x00000000
+  count = 0
+  args = crc.parse_args()
+  dirpath=args.dir
+  while count < args.count :
+    origname = os.urandom (89).encode ("hex")[:-1].strip ("\x00")
+    forgename = crc.forge(wanted_crc, origname, 4)
+    if ("/" not in forgename) and ("\x00" not in forgename):
+      srcpath=dirpath + '/' + str(count)
+      dstpath=dirpath + '/' + forgename
+      file (srcpath, 'a').close()
+      os.rename(srcpath, dstpath)
+      os.system('btrfs fi sync %s' % (dirpath))
+      count+=1;
diff --git a/tests/btrfs/154 b/tests/btrfs/154
new file mode 100755 (executable)
index 0000000..f5dfce4
--- /dev/null
@@ -0,0 +1,64 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2020 Synology.  All Rights Reserved.
+#
+# FS QA Test No. 154
+#
+# Test if btrfs rename handle dir item collision correctly
+# Without patch fix, rename will fail with EOVERFLOW, and filesystem
+# is forced readonly.
+#
+# This bug is going to be fixed by a patch for kernel titled
+# "btrfs: correctly calculate item size used when item key collision happens"
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1       # failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+       cd /
+       rm -f $tmp.*
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+
+# real QA test starts here
+
+_supported_fs btrfs
+_require_scratch
+_require_command $PYTHON2_PROG python2
+
+rm -f $seqres.full
+
+# Currently in btrfs the node/leaf size can not be smaller than the page
+# size (but it can be greater than the page size). So use the largest
+# supported node/leaf size (64Kb) so that the test can run on any platform
+# that Linux supports.
+_scratch_mkfs "--nodesize 65536" >>$seqres.full 2>&1
+_scratch_mount
+
+#
+# In the following for loop, we'll create a leaf fully occupied by
+# only one dir item with many forged collision names in it.
+#
+# leaf 22544384 items 1 free space 0 generation 6 owner FS_TREE
+# leaf 22544384 flags 0x1(WRITTEN) backref revision 1
+# fs uuid 9064ba52-3d2c-4840-8e26-35db08fa17d7
+# chunk uuid 9ba39317-3159-46c9-a75a-965ab1e94267
+#    item 0 key (256 DIR_ITEM 3737737011) itemoff 25 itemsize 65410
+#    ...
+#
+
+$PYTHON2_PROG $here/src/btrfs_crc32c_forged_name.py -d $SCRATCH_MNT -c 310
+echo "Silence is golden"
+
+# success, all done
+status=0; exit
diff --git a/tests/btrfs/154.out b/tests/btrfs/154.out
new file mode 100644 (file)
index 0000000..a18c304
--- /dev/null
@@ -0,0 +1,2 @@
+QA output created by 154
+Silence is golden
index d18450c7552eee869ac70a49ca1f2d3bcf0d5cca..44d33222def02cddd3dcea96726d98994087140e 100644 (file)
 151 auto quick volume
 152 auto quick metadata qgroup send
 153 auto quick qgroup limit
+154 auto quick
 155 auto quick send
 156 auto quick trim balance
 157 auto quick raid