mirror of
https://github.com/linux-apfs/apfstests.git
synced 2026-05-01 15:01:44 -07:00
0804dc1736
Fuzz every field of every structure and then try to write the filesystem, to see how many of these writes can crash the kernel. Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com> Reviewed-by: Eryu Guan <guaneryu@gmail.com> Signed-off-by: Eryu Guan <guaneryu@gmail.com>
315 lines
8.5 KiB
Plaintext
315 lines
8.5 KiB
Plaintext
##/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
|
|
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
|
|
}
|