| #!/bin/bash |
| # Copyright 2025 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| # |
| # Produces a small test image for use with f2fs_reader. |
| # |
| # Requires: |
| # * root. |
| # * fscryptctl (https://github.com/google/fscryptctl/) |
| # |
| # Produces ../testdata/f2fs.img.zst |
| # |
| # Usage: |
| # # sudo ${PWD}/mk_test_img.sh |
| # |
| set -e |
| PATH=${PATH}:/usr/local/bin/ |
| |
| # Prerequisites. |
| apt-get install f2fs-tools zstd fsverity |
| modprobe f2fs |
| rm -f /tmp/f2fs.img ../testdata/f2fs.img.zst |
| |
| # Build empty image. |
| dd if=/dev/zero bs=4096 count=65536 of=/tmp/f2fs.img |
| # TODO(https://fxbug.dev/452741473): Remove hardcoded UUID when it can be properly passed. |
| mkfs.f2fs -f -U 4b92e630-84a5-4461-8df7-16f299ab9926 -O encrypt,verity -l testimage /tmp/f2fs.img |
| |
| # Mount and populate. |
| MOUNT_PATH=/tmp/f2fs_mnt |
| mkdir -p ${MOUNT_PATH} |
| mount -o loop -t f2fs /tmp/f2fs.img ${MOUNT_PATH} |
| |
| REGULAR_PATH=${MOUNT_PATH}/a/b/c |
| mkdir -p ${REGULAR_PATH} |
| echo "inline_data" > ${REGULAR_PATH}/inlined |
| dd if=/dev/zero bs=4096 count=8 of=${REGULAR_PATH}/regular |
| echo -n "01234567" >> ${REGULAR_PATH}/regular |
| |
| ln -s regular ${REGULAR_PATH}/symlink |
| ln ${REGULAR_PATH}/regular ${REGULAR_PATH}/hardlink |
| touch ${REGULAR_PATH}/chowned |
| chown 999:999 ${REGULAR_PATH}/chowned |
| |
| # Large directory (2,000 entries) |
| mkdir ${MOUNT_PATH}/large_dir |
| for i in $(seq 0 2000); do |
| touch ${MOUNT_PATH}/large_dir/${i} |
| done |
| |
| # Large directory with deleted files. |
| mkdir ${MOUNT_PATH}/large_dir2 |
| for i in $(seq 0 2000); do |
| touch ${MOUNT_PATH}/large_dir2/${i} |
| done |
| for i in $(seq 0 1999); do |
| rm ${MOUNT_PATH}/large_dir2/${i} |
| done |
| |
| # Sparse files across nids. |
| # from i_addrs |
| echo -n "foo" > ${MOUNT_PATH}/sparse.dat |
| # from nids[0] n = 923 |
| dd conv=notrunc if=/dev/zero bs=4096 count=1 seek=923 of=${MOUNT_PATH}/sparse.dat |
| # from nids[1] n += 1018 = 1941 |
| dd conv=notrunc if=/dev/zero bs=4096 count=1 seek=1941 of=${MOUNT_PATH}/sparse.dat |
| # from nids[2] n += 1018 = 2959 |
| dd conv=notrunc if=/dev/zero bs=4096 count=1 seek=2959 of=${MOUNT_PATH}/sparse.dat |
| # from nids[3] n += 1018^2 = 1039283 |
| dd conv=notrunc if=/dev/zero bs=4096 count=1 seek=1039283 of=${MOUNT_PATH}/sparse.dat |
| # from nids[4] n += 1018^2 * 100 = 104671683 |
| dd conv=notrunc if=/dev/zero bs=4096 count=1 seek=104671683 of=${MOUNT_PATH}/sparse.dat |
| echo -n "bar" >> ${MOUNT_PATH}/sparse.dat |
| |
| # xattr |
| attr -s a -V "value" ${MOUNT_PATH}/sparse.dat |
| attr -s b -V "value" ${MOUNT_PATH}/sparse.dat |
| attr -s c -V "value" ${MOUNT_PATH}/sparse.dat |
| attr -r b ${MOUNT_PATH}/sparse.dat |
| |
| # xattr padding tests |
| # 1 byte value -> 3 bytes padding |
| attr -s padding_test_1 -V "v" ${MOUNT_PATH}/sparse.dat |
| # 2 byte value -> 2 bytes padding |
| attr -s padding_test_2 -V "va" ${MOUNT_PATH}/sparse.dat |
| # 3 byte value -> 1 byte padding |
| attr -s padding_test_3 -V "val" ${MOUNT_PATH}/sparse.dat |
| # 4 byte value -> 0 bytes padding |
| attr -s padding_test_4 -V "valu" ${MOUNT_PATH}/sparse.dat |
| # 5 byte value -> 3 bytes padding |
| attr -s padding_test_5 -V "value" ${MOUNT_PATH}/sparse.dat |
| |
| VERITY_PATH=${MOUNT_PATH}/verity |
| mkdir -p ${VERITY_PATH} |
| #Enable verity on a file that is normally inlined, but will not be after setting verity. |
| echo "inline_data" > ${VERITY_PATH}/inlined |
| fsverity enable ${VERITY_PATH}/inlined |
| #Enable verity on an otherwise normal file. |
| dd if=/dev/zero bs=4096 count=8 of=${VERITY_PATH}/regular |
| echo -n "01234567" >> ${VERITY_PATH}/regular |
| fsverity enable ${VERITY_PATH}/regular |
| |
| # Enable verity on a large file. The digest will include all the zeroed areas making for a large |
| # merkle tree, to ensure that we can support however they handle layers. Not using the above |
| # sparse file because it is huge and would create many MB of merkle tree. |
| echo -n "foo" > ${VERITY_PATH}/merkle_layers.dat |
| dd conv=notrunc if=/dev/zero bs=4096 count=1 seek=129 of=${VERITY_PATH}/merkle_layers.dat |
| echo -n "bar" >> ${VERITY_PATH}/merkle_layers.dat |
| fsverity enable ${VERITY_PATH}/merkle_layers.dat |
| |
| # fscrypt |
| # |
| # We will use a hard-coded 512-bit key of all zeros for this test. |
| KEY_IDENTIFIER=$(dd if=/dev/zero bs=1 count=64 status=none | fscryptctl add_key ${MOUNT_PATH}) |
| |
| mkdir ${MOUNT_PATH}/fscrypt |
| fscryptctl set_policy --padding=16 --iv-ino-lblk-32 ${KEY_IDENTIFIER} ${MOUNT_PATH}/fscrypt |
| |
| # Track inode number to get the encrypted names out later. |
| declare -A INODES |
| |
| mkdir -p ${MOUNT_PATH}/fscrypt/a/b |
| INODES["$(stat -c "%i" ${MOUNT_PATH}/fscrypt/a)"]="a" |
| INODES["$(stat -c "%i" ${MOUNT_PATH}/fscrypt/a/b)"]="b" |
| # Nb: encrypted files should never be inlined. |
| # The following data is more than 16 bytes to ensure that we validate the xts tweak during decoding. |
| echo -n "test45678abcdef_12345678" > ${MOUNT_PATH}/fscrypt/a/b/inlined |
| dd if=/dev/zero bs=4096 count=1 of=${MOUNT_PATH}/fscrypt/a/b/regular |
| echo "asdf" >> ${MOUNT_PATH}/fscrypt/a/b/regular |
| #Enable verity on a "regular" encrypted file. |
| fsverity enable ${MOUNT_PATH}/fscrypt/a/b/regular |
| ln -s "inlined" ${MOUNT_PATH}/fscrypt/a/b/symlink |
| INODES["$(stat -c "%i" ${MOUNT_PATH}/fscrypt/a/b/symlink)"]="symlink" |
| |
| # Test filenames of different lengths to ensure we use a compatible proxy |
| # filename scheme. |
| LONG_NAME_16=xxxxxxxxyyyyyyyy |
| LONG_NAME_32=${LONG_NAME_16}${LONG_NAME_16} |
| LONG_NAME_64=${LONG_NAME_32}${LONG_NAME_32} |
| LONG_NAME_128=${LONG_NAME_64}${LONG_NAME_64} |
| LONG_NAME_192=${LONG_NAME_128}${LONG_NAME_64} |
| touch ${MOUNT_PATH}/fscrypt/1 |
| touch ${MOUNT_PATH}/fscrypt/12 |
| touch ${MOUNT_PATH}/fscrypt/123 |
| touch ${MOUNT_PATH}/fscrypt/1234 |
| touch ${MOUNT_PATH}/fscrypt/12345 |
| touch ${MOUNT_PATH}/fscrypt/${LONG_NAME_192} |
| |
| # A large amount of data that we need to copy (not fscrypt, needs encrypting) |
| echo "large zero..." |
| dd if=/dev/zero bs=4096 count=4096 of=${MOUNT_PATH}/large_zero |
| # A fscrypt file (no copy - already encrypted) |
| echo "large zero in fscrypt..." |
| dd if=/dev/zero bs=4096 count=4096 of=${MOUNT_PATH}/fscrypt/large_zero |
| |
| # fscrypt nested directories |
| echo "deep nesting fscrypt..." |
| $( |
| cd ${MOUNT_PATH}/fscrypt/ |
| for i in $(seq 0 400); do |
| mkdir d |
| cd d |
| done |
| touch f |
| ) |
| |
| # Args: "decrypted_name" "file_path1" "file_path2" ... |
| lookup_inode() { |
| local target="${1}" |
| shift 1 |
| for f in $@ |
| do |
| if [[ ${INODES["$(stat -c "%i" $f)"]} == "${target}" ]] |
| then |
| echo $(basename $f) |
| return 0 |
| fi |
| done |
| "Inode for '${target}' not found" >&2 |
| return 1 |
| } |
| |
| # Remove the key and get the encrypted names back from the inodes. |
| fscryptctl remove_key ${KEY_IDENTIFIER} ${MOUNT_PATH} |
| str_a=$(lookup_inode "a" ${MOUNT_PATH}/fscrypt/*) |
| echo "let str_a = \"${str_a}\";" |
| |
| str_b=$(lookup_inode "b" ${MOUNT_PATH}/fscrypt/${str_a}/*) |
| echo "let str_b = \"${str_b}\";" |
| |
| str_symlink=$(lookup_inode "symlink" ${MOUNT_PATH}/fscrypt/${str_a}/${str_b}/*) |
| echo "let str_symlink = \"${str_symlink}\";" |
| echo -n "let bytes_symlink_content = " |
| echo "b\"$(readlink ${MOUNT_PATH}/fscrypt/${str_a}/${str_b}/${str_symlink})\";" |
| |
| echo "Expected:" |
| for f in ${MOUNT_PATH}/fscrypt/* |
| do |
| echo "\"$(basename $f)\"", |
| done |
| |
| umount ${MOUNT_PATH} |
| zstd /tmp/f2fs.img -o ../testdata/f2fs.img.zst |
| |
| echo "Done!" |