blob: 10fa428367eb0d985952281b2d1032a314070dad [file] [log] [blame]
##/bin/bash
# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2017 Oracle. All Rights Reserved.
#
# Routines for fuzzing and scrubbing a filesystem.
# Modify various files after a fuzzing operation
_scratch_fuzz_modify() {
echo "+++ stressing filesystem"
mkdir -p $SCRATCH_MNT/data
_xfs_force_bdev data $SCRATCH_MNT/data
$FSSTRESS_PROG -n $((TIME_FACTOR * 10000)) -p $((LOAD_FACTOR * 4)) -d $SCRATCH_MNT/data
is_rt="$($XFS_INFO_PROG "$SCRATCH_MNT" | grep -c 'rtextents=[1-9]')"
if [ $is_rt -gt 0 ]; then
mkdir -p $SCRATCH_MNT/rt
_xfs_force_bdev realtime $SCRATCH_MNT/data
$FSSTRESS_PROG -n $((TIME_FACTOR * 10000)) -p $((LOAD_FACTOR * 4)) -d $SCRATCH_MNT/rt
else
echo "+++ xfs realtime not configured"
fi
}
# Try to access files after fuzzing
_scratch_fuzz_test() {
echo "+++ ls -laR" >> $seqres.full
ls -laR "${SCRATCH_MNT}/test.1/" >/dev/null 2>&1
echo "+++ cat files" >> $seqres.full
(find "${SCRATCH_MNT}/test.1/" -type f -size -1048576k -print0 | xargs -0 cat) >/dev/null 2>&1
}
# Do we have an online scrub program?
_require_scrub() {
case "${FSTYP}" in
"xfs")
test -x "$XFS_SCRUB_PROG" || _notrun "xfs_scrub not found"
;;
*)
_notrun "No online scrub program for ${FSTYP}."
;;
esac
}
# Scrub the scratch filesystem metadata (online)
_scratch_scrub() {
case "${FSTYP}" in
"xfs")
$XFS_SCRUB_PROG -d -T -v "$@" $SCRATCH_MNT
;;
*)
_fail "No online scrub program for ${FSTYP}."
;;
esac
}
# Expand indexed keys (i.e. arrays) into a long format so that we can filter
# the array indices individually, and pass regular keys right through.
#
# For example, "u3.bmx[0-1] = [foo,bar]" is exploded into:
# u3.bmx[0] = [foo,bar]
# u3.bmx[1] = [foo,bar]
#
# Note that we restrict array indices to [0-9] to reduce fuzz runtime. The
# minimum and maximum array indices can be changed by setting the variables
# SCRATCH_XFS_{MIN,MAX}_ARRAY_IDX.
#
# Also filter padding fields.
__explode_xfs_db_fields() {
local min_idx="${SCRATCH_XFS_MIN_ARRAY_IDX}"
local max_idx="${SCRATCH_XFS_MAX_ARRAY_IDX}"
test -z "${min_idx}" && min_idx=0
test -z "${max_idx}" && max_idx=9
test "${max_idx}" = "none" && max_idx=99999
grep ' = ' | \
sed -e 's/^\([.a-zA-Z0-9_]*\)\[\([0-9]*\)-\([0-9]*\)\]\(.*\) = \(.*\)$/\1[%d]\4 \2 \3 = \5/g' \
-e 's/^\([.a-zA-Z0-9_]*\)\[\([0-9]*\)\]\(.*\) = \(.*\)$/\1[%d]\3 \2 \2 = \4/g' | \
while read name col1 col2 rest; do
if [[ "${name}" == *pad* ]]; then
continue
fi
if [ "${col1}" = "=" ]; then
echo "${name} ${col1} ${col2} ${rest}"
continue
fi
test "${min_idx}" -gt "${col1}" && col1="${min_idx}"
test "${max_idx}" -lt "${col2}" && col2="${max_idx}"
seq "${col1}" "${col2}" | while read idx; do
printf "${name} %s\n" "${idx}" "${rest}"
done
done
}
# 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
__explode_xfs_db_fields | while read key equals value; do
fuzzkey="$(echo "${key}")"
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
else
echo "${fuzzkey}"
fi
done | egrep "${filter}" | egrep -v '(^|\.)(sec|nsec|lsn)$'
}
# 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_PROG "${POPULATE_METADUMP}" "${SCRATCH_DEV}"
}
__fuzz_notify() {
echo '========================================'
echo "$@"
echo '========================================'
test -w /dev/ttyprintk && echo "$@" >> /dev/ttyprintk
}
# Perform the online repair part of a fuzz test.
__scratch_xfs_fuzz_field_online() {
local fuzz_action="$1"
# Mount or else we can't do anything online
__fuzz_notify "+ Mount filesystem to try online repair"
_try_scratch_mount 2>&1
res=$?
if [ $res -ne 0 ]; then
(>&2 echo "${fuzz_action}: mount failed ($res).")
return 1
fi
# Make sure online scrub will catch whatever we fuzzed
__fuzz_notify "++ Detect fuzzed field (online)"
_scratch_scrub -n -a 1 -e continue 2>&1
res=$?
test $res -eq 0 && \
(>&2 echo "${fuzz_action}: online scrub didn't fail.")
# Does the health status report reflect the corruption?
if [ $res -ne 0 ]; then
__fuzz_notify "++ Detect fuzzed field ill-health report"
_check_xfs_health $SCRATCH_MNT 2>&1
res=$?
test $res -ne 1 && \
(>&2 echo "${fuzz_action}: online health check failed ($res).")
fi
# Try fixing the filesystem online
__fuzz_notify "++ Try to repair filesystem (online)"
_scratch_scrub 2>&1
res=$?
test $res -ne 0 && \
(>&2 echo "${fuzz_action}: online repair failed ($res).")
# Online scrub should pass now
__fuzz_notify "++ Make sure error is gone (online)"
_scratch_scrub -n -a 1 -e continue 2>&1
res=$?
test $res -ne 0 && \
(>&2 echo "${fuzz_action}: online re-scrub failed ($res).")
__scratch_xfs_fuzz_unmount
# Offline scrub should pass now
__fuzz_notify "+ Make sure error is gone (offline)"
_scratch_xfs_repair -P -n 2>&1
res=$?
test $res -ne 0 && \
(>&2 echo "${fuzz_action}: offline re-scrub failed ($res).")
return 0
}
# Perform the offline repair part of a fuzz test.
__scratch_xfs_fuzz_field_offline() {
local fuzz_action="$1"
# Make sure offline scrub will catch whatever we fuzzed
__fuzz_notify "+ Detect fuzzed field (offline)"
_scratch_xfs_repair -P -n 2>&1
res=$?
test $res -eq 0 && \
(>&2 echo "${fuzz_action}: offline scrub didn't fail.")
# Make sure xfs_repair catches at least as many things as the old
# xfs_check did.
if [ -n "${SCRATCH_XFS_FUZZ_CHECK}" ]; then
__fuzz_notify "+ Detect fuzzed field (xfs_check)"
_scratch_xfs_check 2>&1
res1=$?
if [ $res1 -ne 0 ] && [ $res -eq 0 ]; then
(>&2 echo "${fuzz_action}: xfs_repair passed but xfs_check failed ($res1).")
fi
fi
# Repair the filesystem offline
__fuzz_notify "+ Try to repair the filesystem (offline)"
_repair_scratch_fs 2>&1
res=$?
test $res -ne 0 && \
(>&2 echo "${fuzz_action}: offline repair failed ($res).")
# See if repair finds a clean fs
__fuzz_notify "+ Make sure error is gone (offline)"
_scratch_xfs_repair -P -n 2>&1
res=$?
test $res -ne 0 && \
(>&2 echo "${fuzz_action}: offline re-scrub failed ($res).")
return 0
}
# Perform the no-repair part of a fuzz test.
__scratch_xfs_fuzz_field_norepair() {
local fuzz_action="$1"
# Make sure offline scrub will catch whatever we fuzzed
__fuzz_notify "+ Detect fuzzed field (offline)"
_scratch_xfs_repair -P -n 2>&1
res=$?
test $res -eq 0 && \
(>&2 echo "${fuzz_action}: offline scrub didn't fail.")
# Mount or else we can't do anything in norepair mode
__fuzz_notify "+ Mount filesystem to try online scan"
_try_scratch_mount 2>&1
res=$?
if [ $res -ne 0 ]; then
(>&2 echo "${fuzz_action}: mount failed ($res).")
return 1
fi
# Make sure online scrub will catch whatever we fuzzed
__fuzz_notify "++ Detect fuzzed field (online)"
_scratch_scrub -n -a 1 -e continue 2>&1
res=$?
test $res -eq 0 && \
(>&2 echo "${fuzz_action}: online scrub didn't fail.")
# Does the health status report reflect the corruption?
if [ $res -ne 0 ]; then
__fuzz_notify "++ Detect fuzzed field ill-health report"
_check_xfs_health $SCRATCH_MNT 2>&1
res=$?
test $res -ne 1 && \
(>&2 echo "${fuzz_action}: online health check failed ($res).")
fi
__scratch_xfs_fuzz_unmount
return 0
}
# Perform the online-then-offline repair part of a fuzz test.
__scratch_xfs_fuzz_field_both() {
local fuzz_action="$1"
# Make sure offline scrub will catch whatever we fuzzed
__fuzz_notify "+ Detect fuzzed field (offline)"
_scratch_xfs_repair -P -n 2>&1
res=$?
test $res -eq 0 && \
(>&2 echo "${fuzz_action}: offline scrub didn't fail.")
# Mount or else we can't do anything in both repair mode
__fuzz_notify "+ Mount filesystem to try both repairs"
_try_scratch_mount 2>&1
res=$?
if [ $res -ne 0 ]; then
(>&2 echo "${fuzz_action}: mount failed ($res).")
else
# Make sure online scrub will catch whatever we fuzzed
__fuzz_notify "++ Detect fuzzed field (online)"
_scratch_scrub -n -a 1 -e continue 2>&1
res=$?
test $res -eq 0 && \
(>&2 echo "${fuzz_action}: online scrub didn't fail.")
# Does the health status report reflect the corruption?
if [ $res -ne 0 ]; then
__fuzz_notify "++ Detect fuzzed field ill-health report"
_check_xfs_health $SCRATCH_MNT 2>&1
res=$?
test $res -ne 1 && \
(>&2 echo "${fuzz_action}: online health check failed ($res).")
fi
# Try fixing the filesystem online
__fuzz_notify "++ Try to repair filesystem (online)"
_scratch_scrub 2>&1
res=$?
test $res -ne 0 && \
(>&2 echo "${fuzz_action}: online repair failed ($res).")
__scratch_xfs_fuzz_unmount
fi
# Repair the filesystem offline if online repair failed?
if [ $res -ne 0 ]; then
__fuzz_notify "+ Try to repair the filesystem (offline)"
_repair_scratch_fs 2>&1
res=$?
test $res -ne 0 && \
(>&2 echo "${fuzz_action}: offline repair failed ($res).")
fi
# See if repair finds a clean fs
__fuzz_notify "+ Make sure error is gone (offline)"
_scratch_xfs_repair -P -n 2>&1
res=$?
test $res -ne 0 && \
(>&2 echo "${fuzz_action}: offline re-scrub failed ($res).")
# Mount so that we can see what scrub says after we've fixed the fs
__fuzz_notify "+ Re-mount filesystem to re-try online scan"
_try_scratch_mount 2>&1
res=$?
if [ $res -ne 0 ]; then
(>&2 echo "${fuzz_action}: mount failed ($res).")
return 1
fi
# Online scrub should pass now
__fuzz_notify "++ Make sure error is gone (online)"
_scratch_scrub -n -a 1 -e continue 2>&1
res=$?
test $res -ne 0 && \
(>&2 echo "${fuzz_action}: online re-scrub failed ($res).")
__scratch_xfs_fuzz_unmount
return 0
}
# Assess the state of the filesystem after a repair strategy has been run by
# trying to make changes to it.
_scratch_xfs_fuzz_field_modifyfs() {
local fuzz_action="$1"
local repair="$2"
# Try to mount the filesystem so that we can make changes
__fuzz_notify "+ Mount filesystem to make changes"
_try_scratch_mount 2>&1
res=$?
if [ $res -ne 0 ]; then
(>&2 echo "${fuzz_action}: pre-mod mount failed ($res).")
return $res
fi
# Try modifying the filesystem again
__fuzz_notify "++ Try to write filesystem again"
_scratch_fuzz_modify 2>&1
# If we didn't repair anything, there's no point in checking further,
# the fs is still corrupt.
if [ "${repair}" = "none" ]; then
__scratch_xfs_fuzz_unmount
return 0
fi
# Run an online check to make sure the fs is still ok, unless we
# are running the norepair strategy.
__fuzz_notify "+ Re-check the filesystem (online)"
_scratch_scrub -n -e continue 2>&1
res=$?
test $res -ne 0 && \
(>&2 echo "${fuzz_action}: online post-mod scrub failed ($res).")
__scratch_xfs_fuzz_unmount
# Run an offline check to make sure the fs is still ok, unless we
# are running the norepair strategy.
__fuzz_notify "+ Re-check the filesystem (offline)"
_scratch_xfs_repair -P -n 2>&1
res=$?
test $res -ne 0 && \
(>&2 echo "${fuzz_action}: offline post-mod scrub failed ($res).")
return 0
}
# 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}"
_scratch_xfs_fuzz_metadata_field "${field}" ${fuzzverb} "$@"
res=$?
test $res -ne 0 && return
# Try to catch the error with whatever repair strategy we picked.
# The fs should not be mounted before or after the strategy call.
local fuzz_action="${field} = ${fuzzverb}"
case "${repair}" in
"online")
__scratch_xfs_fuzz_field_online "${fuzz_action}"
res=$?
;;
"offline")
__scratch_xfs_fuzz_field_offline "${fuzz_action}"
res=$?
;;
"none")
__scratch_xfs_fuzz_field_norepair "${fuzz_action}"
res=$?
;;
"both")
__scratch_xfs_fuzz_field_both "${fuzz_action}"
res=$?
;;
*)
(>&2 echo "unknown repair strategy ${repair}.")
res=2
;;
esac
test $res -eq 0 || return $res
# See what happens when we modify the fs
_scratch_xfs_fuzz_field_modifyfs "${fuzz_action}" "${repair}"
return $?
}
# 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' | \
grep -v '^random$'
}
# 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
}