[PATCH 0/1] udf: fix silent AED tagLocation corruption

From: Steve Magnani
Date: Thu Jan 07 2021 - 18:43:21 EST


From: Steven J. Magnani <magnani@xxxxxxxx>

Certain scenarios involving enlargement of a large and/or fragmented file
result in corruption of the file's Allocation Extent Descriptor tag.
Once this has happened, attempts to read the file in Windows 10 fail with
"Data error (cyclic redundancy check)". A "chkdsk /f" in Windows causes
extents of the file described by the corrupted AED (and any subsequent
chained AEDs) to be truncated.

No problems are noted when the file is accessed in Linux.

The script below creates a toy UDF filesystem to illustrate the problem.
The filesystem can be dd'd to a flash disk partition of the same size
to observe how Windows handles the corruption.
---
#!/bin/bash

# Tool to illustrate / test fix for bug in UDF driver
# that can result in corruption of Allocation Extent Descriptor tag location
# Developed using mkudffs from udftools 2.2.

# Terminology:
# LSN == Logical Sector Number (media / volume relative)
# LBN == Logical Block Number (UDF partition relative)


TEST_UDFFS=/tmp/test.udf

# Changing these may alter filesystem layout and/or invalidate the test
UDFFS_SIZE=1M # --size argument to 'truncate' command
UDF_BLOCKSIZE=512
PD_LSN=98 # Expected UDF Partition Descriptor location
AED_LSN=1343 # Location of Allocation Extent Descriptor under test

require()
{
local APP_REALPATH=`which $1`
local PACKAGE_NAME=${2:-$1}
if [ -z "$APP_REALPATH" ] ; then
echo This test requires $1. Please install the $PACKAGE_NAME package.
exit 1
fi
}

# "Quiet" dd command
ddq()
{
dd $* 2> /dev/null
}

# Extract a 16-bit little-endian unsigned value at a specified byte offset ($2)
# of a specified LSN ($1)
#
extract16()
{
local value=`ddq if=$TEST_UDFFS bs=$UDF_BLOCKSIZE skip=$1 count=1 | ddq bs=1 skip=$2 count=2 | od -A none -t u2 --endian=little`
[ -z "$value" ] && value=0 # Fail in a sane manner

echo -n $value
}

# Extract a 32-bit little-endian unsigned value at a specified byte offset ($2)
# of a specified LSN ($1)
#
extract32()
{
local value=`ddq if=$TEST_UDFFS bs=$UDF_BLOCKSIZE skip=$1 count=1 | ddq bs=1 skip=$2 count=4 | od -A none -t u4 --endian=little`
[ -z "$value" ] && value=0 # Fail in a sane manner

echo -n $value
}


# $1 = LSN
# $2 = Expected tag ID value (decimal) - i.e., 266 for EFE
require_tag_id()
{
local found_tag=`extract16 $1 0`

if [ $found_tag -ne $2 ] ; then
echo Expected tag $2 in LSN $1, found $found_tag.
echo Unexpected test conditions - results must be verified manually
exit 1
fi
}

gen_provoker_data()
{
ddq if=/dev/zero bs=$UDF_BLOCKSIZE count=1 of=/tmp/hole.$$

openssl rand 172032
cat /tmp/hole.$$
openssl rand 149504
cat /tmp/hole.$$
openssl rand 22528
cat /tmp/hole.$$
openssl rand 2048
cat /tmp/hole.$$
openssl rand 4096
cat /tmp/hole.$$
openssl rand 4096
cat /tmp/hole.$$
openssl rand 8192
cat /tmp/hole.$$
openssl rand 184320
cat /tmp/hole.$$
openssl rand 131072

rm -f /tmp/hole.$$
}

# $1 == loopback UDF filesystem to create
make_test_udf()
{
rm -f $1
truncate --size=$UDFFS_SIZE $1
mkudffs --label=SPARSE --uid=$UID --blocksize=$UDF_BLOCKSIZE $1 > /dev/null

mkdir -p /tmp/testudf.mnt
echo Mounting test UDF filesystem. Please provide the root password if prompted.
sudo mount $1 /tmp/testudf.mnt
cp --sparse=always /tmp/provoker.$$ /tmp/testudf.mnt/provoker
sync
sudo umount /tmp/testudf.mnt
rmdir /tmp/testudf.mnt
echo Test UDF filesystem generated in $1.
}

### MAIN

require openssl
require mkudffs udftools

if [ -e $TEST_UDFFS ] ; then
echo $TEST_UDFFS already exists - please dismount and remove it
exit 1
fi

gen_provoker_data > /tmp/provoker.$$
make_test_udf $TEST_UDFFS

# Verify hardcoded LSNs involved in testing
require_tag_id $PD_LSN 5 # PD
require_tag_id $AED_LSN 258 # AED

LBN_BASE=`extract32 $PD_LSN 188` # Partition Starting Location
AED_LBN=`expr $AED_LSN - $LBN_BASE`
AED_TAG_LOCATION=`extract32 $AED_LSN 12`

if [ $AED_TAG_LOCATION -ne $AED_LBN ] ; then
echo Test FAILED: expected AED tag location $AED_LBN, actual is $AED_TAG_LOCATION
exit 1
else
echo Test PASSED. AED tag location field is correct.
fi
------------------------------------------------------------------------
Steven J. Magnani "I claim this network for MARS!
Earthling, return my space modulator!"
#include <standard.disclaimer>