##/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() { nr="$1" test -z "${nr}" && nr=50000 echo "+++ touch ${nr} files" blk_sz=$(stat -f -c '%s' ${SCRATCH_MNT}) $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" done sync rm -rf "/tmp/afile" echo "+++ create files" mkdir -p "${SCRATCH_MNT}/test.moo" $XFS_IO_PROG -f -c 'pwrite -S 0x80 0 65536' "${SCRATCH_MNT}/test.moo/urk" > /dev/null sync echo "+++ remove files" rm -rf "${SCRATCH_MNT}/test.moo" } # 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 } # 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}") 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 }