##/bin/bash
-
-# Routines for fuzzing and scrubbing a filesystem.
-#
-#-----------------------------------------------------------------------
-# Copyright (c) 2017 Oracle. All Rights Reserved.
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2017 Oracle. All Rights Reserved.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-# USA
-#-----------------------------------------------------------------------
+# Routines for fuzzing and scrubbing a filesystem.
# Modify various files after a fuzzing operation
_scratch_fuzz_modify() {
$XFS_IO_PROG -f -c "pwrite -S 0x63 0 ${blk_sz}" "/tmp/afile" > /dev/null
date="$(date)"
find "${SCRATCH_MNT}/" -type f 2> /dev/null | head -n "${nr}" | while read f; do
+ # try to remove append, immutable (and even dax) flag if exists
+ $XFS_IO_PROG -rc 'chattr -x -i -a' "$f" > /dev/null 2>&1
setfattr -n "user.date" -v "${date}" "$f"
cat "/tmp/afile" >> "$f"
mv "$f" "$f.longer"
echo "+++ create files"
mkdir -p "${SCRATCH_MNT}/test.moo"
- $XFS_IO_PROG -f -c 'pwrite -S 0x80 0 65536' "${SCRATCH_MNT}/test.moo/urk"
+ $XFS_IO_PROG -f -c 'pwrite -S 0x80 0 65536' "${SCRATCH_MNT}/test.moo/urk" > /dev/null
sync
echo "+++ remove files"
# Do we have an online scrub program?
_require_scrub() {
case "${FSTYP}" in
- "xfs"|"ext4")
+ "xfs")
test -x "$XFS_SCRUB_PROG" || _notrun "xfs_scrub not found"
;;
*)
# Scrub the scratch filesystem metadata (online)
_scratch_scrub() {
case "${FSTYP}" in
- "xfs"|"ext4"|"ext3"|"ext2")
+ "xfs")
$XFS_SCRUB_PROG -d -T -v "$@" $SCRATCH_MNT
;;
*)
;;
esac
}
+
+# Filter out any keys with an array index >= 10, collapse any array range
+# ("[1-195]") to the first item, and ignore padding fields.
+__filter_xfs_db_keys() {
+ sed -e '/\([a-z]*\)\[\([0-9][0-9]\+\)\].*/d' \
+ -e 's/\([a-zA-Z0-9_]*\)\[\([0-9]*\)-[0-9]*\]/\1[\2]/g' \
+ -e '/pad/d'
+}
+
+# Filter the xfs_db print command's field debug information
+# into field name and type.
+__filter_xfs_db_print_fields() {
+ filter="$1"
+ if [ -z "${filter}" ] || [ "${filter}" = "nofilter" ]; then
+ filter='^'
+ fi
+ grep ' = ' | while read key equals value; do
+ fuzzkey="$(echo "${key}" | __filter_xfs_db_keys)"
+ if [ -z "${fuzzkey}" ]; then
+ continue
+ elif [[ "${value}" == "["* ]]; then
+ echo "${value}" | sed -e 's/^.//g' -e 's/.$//g' -e 's/,/\n/g' | while read subfield; do
+ echo "${fuzzkey}.${subfield}"
+ done | __filter_xfs_db_keys
+ else
+ echo "${fuzzkey}"
+ fi
+ done | egrep "${filter}"
+}
+
+# Navigate to some part of the filesystem and print the field info.
+# The first argument is an egrep filter for the fields
+# The rest of the arguments are xfs_db commands to locate the metadata.
+_scratch_xfs_list_metadata_fields() {
+ filter="$1"
+ shift
+ if [ -n "${SCRATCH_XFS_LIST_METADATA_FIELDS}" ]; then
+ echo "${SCRATCH_XFS_LIST_METADATA_FIELDS}" | tr '[ ,]' '[\n\n]'
+ return;
+ fi
+
+ local cmds=()
+ for arg in "$@"; do
+ cmds+=("-c" "${arg}")
+ done
+ _scratch_xfs_db "${cmds[@]}" -c print | __filter_xfs_db_print_fields "${filter}"
+}
+
+# Fuzz a metadata field
+# The first arg is the field name
+# The second arg is the xfs_db fuzz verb
+# The rest of the arguments are xfs_db commands to find the metadata.
+_scratch_xfs_fuzz_metadata_field() {
+ key="$1"
+ value="$2"
+ shift; shift
+
+ if [[ "${key}" == *crc ]]; then
+ fuzz_arg="-c"
+ else
+ fuzz_arg="-d"
+ fi
+ oldval="$(_scratch_xfs_get_metadata_field "${key}" "$@")"
+
+ local cmds=()
+ for arg in "$@"; do
+ cmds+=("-c" "${arg}")
+ done
+ while true; do
+ _scratch_xfs_db -x "${cmds[@]}" -c "fuzz ${fuzz_arg} ${key} ${value}"
+ echo
+ newval="$(_scratch_xfs_get_metadata_field "${key}" "$@" 2> /dev/null)"
+ if [ "${key}" != "random" ] || [ "${oldval}" != "${newval}" ]; then
+ break;
+ fi
+ done
+ if [ "${oldval}" = "${newval}" ]; then
+ echo "Field ${key} already set to ${newval}, skipping test."
+ return 1
+ fi
+ return 0
+}
+
+# Try to forcibly unmount the scratch fs
+__scratch_xfs_fuzz_unmount()
+{
+ while _scratch_unmount 2>/dev/null; do sleep 0.2; done
+}
+
+# Restore metadata to scratch device prior to field-fuzzing.
+__scratch_xfs_fuzz_mdrestore()
+{
+ test -e "${POPULATE_METADUMP}" || _fail "Need to set POPULATE_METADUMP"
+
+ __scratch_xfs_fuzz_unmount
+ xfs_mdrestore "${POPULATE_METADUMP}" "${SCRATCH_DEV}"
+}
+
+__fuzz_notify() {
+ echo "$@"
+ test -w /dev/ttyprintk && echo "$@" >> /dev/ttyprintk
+}
+
+# Fuzz one field of some piece of metadata.
+# First arg is the field name
+# Second arg is the fuzz verb (ones, zeroes, random, add, sub...)
+# Third arg is the repair mode (online, offline, both, none)
+__scratch_xfs_fuzz_field_test() {
+ field="$1"
+ fuzzverb="$2"
+ repair="$3"
+ shift; shift; shift
+
+ # Set the new field value
+ __fuzz_notify "+ Fuzz ${field} = ${fuzzverb}"
+ echo "========================"
+ _scratch_xfs_fuzz_metadata_field "${field}" ${fuzzverb} "$@"
+ res=$?
+ test $res -ne 0 && return
+
+ # Try to catch the error with scrub
+ echo "+ Try to catch the error"
+ _try_scratch_mount 2>&1
+ res=$?
+ if [ $res -eq 0 ]; then
+ # Try an online scrub unless we're fuzzing ag 0's sb,
+ # which scrub doesn't know how to fix.
+ if [ "${repair}" != "none" ]; then
+ echo "++ Online scrub"
+ if [ "$1" != "sb 0" ]; then
+ _scratch_scrub -n -a 1 -e continue 2>&1
+ res=$?
+ test $res -eq 0 && \
+ (>&2 echo "scrub didn't fail with ${field} = ${fuzzverb}.")
+ fi
+ fi
+
+ # Try fixing the filesystem online?!
+ if [ "${repair}" = "online" ] || [ "${repair}" = "both" ]; then
+ __fuzz_notify "++ Try to repair filesystem online"
+ _scratch_scrub 2>&1
+ res=$?
+ test $res -ne 0 && \
+ (>&2 echo "online repair failed ($res) with ${field} = ${fuzzverb}.")
+ fi
+
+ __scratch_xfs_fuzz_unmount
+ elif [ "${repair}" = "online" ] || [ "${repair}" = "both" ]; then
+ (>&2 echo "mount failed ($res) with ${field} = ${fuzzverb}.")
+ fi
+
+ # Repair the filesystem offline?
+ if [ "${repair}" = "offline" ] || [ "${repair}" = "both" ]; then
+ echo "+ Try to repair the filesystem offline"
+ _repair_scratch_fs 2>&1
+ res=$?
+ test $res -ne 0 && \
+ (>&2 echo "offline repair failed ($res) with ${field} = ${fuzzverb}.")
+ fi
+
+ # See if repair finds a clean fs
+ if [ "${repair}" != "none" ]; then
+ echo "+ Make sure error is gone (offline)"
+ _scratch_xfs_repair -n 2>&1
+ res=$?
+ test $res -ne 0 && \
+ (>&2 echo "offline re-scrub ($res) with ${field} = ${fuzzverb}.")
+ fi
+
+ # See if scrub finds a clean fs
+ echo "+ Make sure error is gone (online)"
+ _try_scratch_mount 2>&1
+ res=$?
+ if [ $res -eq 0 ]; then
+ # Try an online scrub unless we're fuzzing ag 0's sb,
+ # which scrub doesn't know how to fix.
+ if [ "${repair}" != "none" ]; then
+ echo "++ Online scrub"
+ if [ "$1" != "sb 0" ]; then
+ _scratch_scrub -n -e continue 2>&1
+ res=$?
+ test $res -ne 0 && \
+ (>&2 echo "online re-scrub ($res) with ${field} = ${fuzzverb}.")
+ fi
+ fi
+
+ # Try modifying the filesystem again!
+ __fuzz_notify "++ Try to write filesystem again"
+ _scratch_fuzz_modify 100 2>&1
+ __scratch_xfs_fuzz_unmount
+ else
+ (>&2 echo "re-mount failed ($res) with ${field} = ${fuzzverb}.")
+ fi
+
+ # See if repair finds a clean fs
+ if [ "${repair}" != "none" ]; then
+ echo "+ Re-check the filesystem (offline)"
+ _scratch_xfs_repair -n 2>&1
+ res=$?
+ test $res -ne 0 && \
+ (>&2 echo "re-repair failed ($res) with ${field} = ${fuzzverb}.")
+ fi
+}
+
+# Make sure we have all the pieces we need for field fuzzing
+_require_scratch_xfs_fuzz_fields()
+{
+ _require_scratch_nocheck
+ _require_scrub
+ _require_populate_commands
+ _scratch_mkfs_xfs >/dev/null 2>&1
+ _require_xfs_db_command "fuzz"
+}
+
+# Grab the list of available fuzzing verbs
+_scratch_xfs_list_fuzz_verbs() {
+ if [ -n "${SCRATCH_XFS_LIST_FUZZ_VERBS}" ]; then
+ echo "${SCRATCH_XFS_LIST_FUZZ_VERBS}" | tr '[ ,]' '[\n\n]'
+ return;
+ fi
+ _scratch_xfs_db -x -c 'sb 0' -c 'fuzz' | grep '^Fuzz commands:' | \
+ sed -e 's/[,.]//g' -e 's/Fuzz commands: //g' -e 's/ /\n/g'
+}
+
+# Fuzz some of the fields of some piece of metadata
+# The first argument is an egrep filter for the field names
+# The second argument is the repair mode (online, offline, both)
+# The rest of the arguments are xfs_db commands to locate the metadata.
+#
+# Users can specify the fuzz verbs via SCRATCH_XFS_LIST_FUZZ_VERBS
+# They can specify the fields via SCRATCH_XFS_LIST_METADATA_FIELDS
+_scratch_xfs_fuzz_metadata() {
+ filter="$1"
+ repair="$2"
+ shift; shift
+
+ fields="$(_scratch_xfs_list_metadata_fields "${filter}" "$@")"
+ verbs="$(_scratch_xfs_list_fuzz_verbs)"
+ echo "Fields we propose to fuzz under: $@"
+ echo $(echo "${fields}")
+ echo "Verbs we propose to fuzz with:"
+ echo $(echo "${verbs}")
+
+ # Always capture full core dumps from crashing tools
+ ulimit -c unlimited
+
+ echo "${fields}" | while read field; do
+ echo "${verbs}" | while read fuzzverb; do
+ __scratch_xfs_fuzz_mdrestore
+ __scratch_xfs_fuzz_field_test "${field}" "${fuzzverb}" "${repair}" "$@"
+ done
+ done
+}