diff --git a/common/encrypt b/common/encrypt
index b011c3e8..ce6ebdf9 100644
--- a/common/encrypt
+++ b/common/encrypt
@@ -110,7 +110,7 @@ _require_encryption_policy_support()
 
 	if [ "$FSTYP" = "btrfs" ]; then
 		if (( policy_flags & ~FSCRYPT_POLICY_FLAG_IV_FROM_FS )); then
-			_fail "Btrfs accepts policy flags of IV_FROM_FS only"
+			_notrun "Btrfs accepts policy flags of IV_FROM_FS only"
 		fi
 	fi
 
@@ -635,6 +635,40 @@ _get_encryption_nonce()
 	esac
 }
 
+# Retrieve the encryption IV of the first file extent in an inode as a hex
+# string.  The IV was randomly generated by the filesystem, in the case of
+# btrfs, and isn't exposed directly to userspace.  But it can be read using
+# the filesystem's debugging tools.
+_get_encryption_iv()
+{
+	local device=$1
+	local inode=$2
+
+	case $FSTYP in
+	btrfs)
+		# btrfs prints the file extents (for simple unshared
+		# inodes) like:
+		#         item 21 key ($inode EXTENT_DATA 0) itemoff 2534 itemsize 69
+		#                generation 7 type 1 (regular)
+                #		 extent data disk byte 5304320 nr 1048576
+                #		 extent data offset 0 nr 1048576 ram 1048576
+                #		 extent compression 0 (none)
+                #		 extent encryption 81 (1, 16: IV 77d05501da7c23920f9ca00872f0bbc3)
+
+		#
+		$BTRFS_UTIL_PROG inspect-internal dump-tree $device | \
+			grep -A 5 "key ($inode EXTENT_DATA 0)" | \
+			awk '/ IV [[:xdigit:]]+)/ {
+				match($0, /IV ([[:xdigit:]]+)\)/,a);
+				print a[1];
+			}'
+		;;
+	*)
+		_fail "_get_encryption_iv() isn't implemented on $FSTYP"
+		;;
+	esac
+}
+
 # Require support for _get_encryption_nonce()
 _require_get_encryption_nonce_support()
 {
@@ -669,6 +703,19 @@ _get_ciphertext_filename()
 	local dir_inode=$3
 
 	case $FSTYP in
+	btrfs)
+		# Extract the filename from the inode_ref object, similar to:
+		# item 24 key (259 INODE_REF 257) itemoff 14826 itemsize 26
+		# 	index 3 namelen 16 name: J\xf7\x15tD\x8eL\xae/\x98\x9f\x09\xc1\xb6\x09>
+		#
+		$BTRFS_UTIL_PROG inspect-internal dump-tree $device | \
+			grep -A 1 "key ($inode INODE_REF " | tail -n 1 | \
+			perl -ne '
+				s/.*?name: //;
+				chomp;
+				s/\\x([[:xdigit:]]{2})/chr hex $1/eg;
+				print;'
+		;;
 	ext4)
 		# Extract the filename from the debugfs output line like:
 		#
@@ -806,6 +853,7 @@ _do_verify_ciphertext_for_encryption_policy()
 	local raw_key_hex=$6
 	local crypt_contents_cmd="$here/src/fscrypt-crypt-util $7"
 	local crypt_filename_cmd="$here/src/fscrypt-crypt-util $8"
+	local use_iv=$9
 
 	local blocksize=$(_get_block_size $SCRATCH_MNT)
 	local test_contents_files=()
@@ -860,18 +908,24 @@ _do_verify_ciphertext_for_encryption_policy()
 
 	echo "Verifying encrypted file contents" >> $seqres.full
 	for f in "${test_contents_files[@]}"; do
+		local iv_arg=""
 		read -r src inode blocklist <<< "$f"
 		nonce=$(_get_encryption_nonce $SCRATCH_DEV $inode)
 		_dump_ciphertext_blocks $SCRATCH_DEV $blocklist > $tmp.actual_contents
+		if [ -n "$use_iv" ]; then
+			local iv_hex=$(_get_encryption_iv $SCRATCH_DEV $inode)
+			iv_arg=" --iv=$iv_hex"
+		fi
+			
 		$crypt_contents_cmd $contents_encryption_mode $raw_key_hex \
 			--file-nonce=$nonce --block-size=$blocksize \
-			--inode-number=$inode < $src > $tmp.expected_contents
+			--inode-number=$inode $iv_arg < $src > $tmp.expected_contents
 		if ! cmp $tmp.expected_contents $tmp.actual_contents; then
 			_fail "Expected encrypted contents != actual encrypted contents.  File: $f"
 		fi
 		$crypt_contents_cmd $contents_encryption_mode $raw_key_hex \
 			--decrypt --file-nonce=$nonce --block-size=$blocksize \
-			--inode-number=$inode \
+			--inode-number=$inode $iv_arg \
 			< $tmp.actual_contents > $tmp.decrypted_contents
 		if ! cmp $src $tmp.decrypted_contents; then
 			_fail "Contents decryption sanity check failed.  File: $f"
@@ -957,9 +1011,10 @@ _verify_ciphertext_for_encryption_policy()
 	local crypt_util_contents_args=""
 	local crypt_util_filename_args=""
 	local expected_identifier
+	local use_iv=""
 
 	if [ "$FSTYP" = "btrfs" ]; then
-		policy_version = 2
+		policy_version=2
 	fi
 	
 	shift 2
@@ -999,9 +1054,9 @@ _verify_ciphertext_for_encryption_policy()
 		crypt_util_args+=" --kdf=HKDF-SHA512"
 		if [ "$FSTYP" = "btrfs" ]; then
 			if (( policy_flags & ~FSCRYPT_POLICY_FLAG_IV_FROM_FS )); then
-				_fail "Btrfs accepts policy flags of IV_FROM_FS only"
+				_notrun "Btrfs accepts policy flags of IV_FROM_FS only"
 			fi	
-			policy_flags |= FSCRYPT_POLICY_FLAG_IV_FROM_FS
+			(( policy_flags |= FSCRYPT_POLICY_FLAG_IV_FROM_FS ))
 		fi
 		if (( policy_flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then
 			crypt_util_args+=" --direct-key"
@@ -1009,6 +1064,9 @@ _verify_ciphertext_for_encryption_policy()
 			crypt_util_args+=" --iv-ino-lblk-64"
 		elif (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 )); then
 			crypt_util_args+=" --iv-ino-lblk-32"
+		elif (( policy_flags & FSCRYPT_POLICY_FLAG_IV_FROM_FS )); then
+			crypt_util_args+=" --iv-from-fs"
+			use_iv=1
 		fi
 	else
 		if (( policy_flags & ~FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then
@@ -1026,7 +1084,7 @@ _verify_ciphertext_for_encryption_policy()
 	_require_test_program "fscrypt-crypt-util"
 	_require_xfs_io_command "fiemap"
 	_require_get_encryption_nonce_support
-	_require_get_ciphertext_filename_support
+	#_require_get_ciphertext_filename_support
 	if (( policy_version == 1 )); then
 		_require_command "$KEYCTL_PROG" keyctl
 	fi
@@ -1094,7 +1152,8 @@ EOF
 		"$keyspec" \
 		"$raw_key_hex" \
 		"$crypt_util_contents_args" \
-		"$crypt_util_filename_args"
+		"$crypt_util_filename_args" \
+		"$use_iv"
 }
 
 # Replace no-key filenames in the given directory with "NOKEY_NAME".
diff --git a/src/fscrypt-crypt-util.c b/src/fscrypt-crypt-util.c
index ffb9534d..6f4f8190 100644
--- a/src/fscrypt-crypt-util.c
+++ b/src/fscrypt-crypt-util.c
@@ -76,10 +76,14 @@ static void usage(FILE *fp)
 "  --inode-number=INUM         The file's inode number.  Required for\n"
 "                                --iv-ino-lblk-32 and --iv-ino-lblk-64;\n"
 "                                otherwise is unused.\n"
+"  --iv=IV                     For the IV-from-FS format, the starting IV for\n"
+"                                the file\n"
 "  --iv-ino-lblk-32            Similar to --iv-ino-lblk-64, but selects the\n"
 "                                32-bit variant.\n"
 "  --iv-ino-lblk-64            Use the format where the IVs include the inode\n"
 "                                number and the same key is shared across files.\n"
+"  --iv-from-fs                Use the format where the IV is specifically\n"
+"                                supplied by the filesystem\n"
 "  --kdf=KDF                   Key derivation function to use: AES-128-ECB,\n"
 "                                HKDF-SHA512, or none.  Default: none\n"
 "  --mode-num=NUM              The encryption mode number.  This may be required\n"
@@ -1794,8 +1798,11 @@ struct key_and_iv_params {
 	u8 file_nonce[FILE_NONCE_SIZE];
 	bool file_nonce_specified;
 	bool direct_key;
+	u8 iv[MAX_IV_SIZE];
+	bool iv_set;
 	bool iv_ino_lblk_64;
 	bool iv_ino_lblk_32;
+	bool iv_from_fs;
 	u64 block_number;
 	u64 inode_number;
 	u8 fs_uuid[UUID_SIZE];
@@ -1809,6 +1816,7 @@ struct key_and_iv_params {
 #define HKDF_CONTEXT_DIRHASH_KEY	5
 #define HKDF_CONTEXT_IV_INO_LBLK_32_KEY	6
 #define HKDF_CONTEXT_INODE_HASH_KEY	7
+#define HKDF_CONTEXT_IV_FROM_FS		8
 
 /* Hash the file's inode number using SipHash keyed by a derived key */
 static u32 hash_inode_number(const struct key_and_iv_params *params)
@@ -1881,9 +1889,10 @@ static void derive_real_key(const struct key_and_iv_params *params,
 			info[infolen++] = params->mode_num;
 			memcpy(&info[infolen], params->fs_uuid, UUID_SIZE);
 			infolen += UUID_SIZE;
-		} else {
-			if (!params->file_nonce_specified)
-				die("--kdf=HKDF-SHA512 requires --file-nonce or --iv-ino-lblk-{64,32}");
+		} else if (params->iv_from_fs) {
+			info[infolen++] = HKDF_CONTEXT_IV_FROM_FS;
+			info[infolen++] = params->mode_num;
+		} else if (params->file_nonce_specified) {
 			info[infolen++] = HKDF_CONTEXT_PER_FILE_ENC_KEY;
 			memcpy(&info[infolen], params->file_nonce,
 			       FILE_NONCE_SIZE);
@@ -1906,6 +1915,10 @@ static void generate_iv(const struct key_and_iv_params *params,
 			die("--direct-key requires --file-nonce");
 		iv->block_number = cpu_to_le64(params->block_number);
 		memcpy(iv->nonce, params->file_nonce, FILE_NONCE_SIZE);
+	} else if (params->iv_from_fs) {
+		memcpy(iv->nonce, params->file_nonce, FILE_NONCE_SIZE);
+		if (params->iv_set)
+			memcpy(iv->bytes, params->iv, MAX_IV_SIZE);
 	} else if (params->iv_ino_lblk_64) {
 		if (params->block_number > UINT32_MAX)
 			die("iv-ino-lblk-64 can't use --block-number > UINT32_MAX");
@@ -1987,8 +2000,10 @@ enum {
 	OPT_FS_UUID,
 	OPT_HELP,
 	OPT_INODE_NUMBER,
+	OPT_IV,
 	OPT_IV_INO_LBLK_32,
 	OPT_IV_INO_LBLK_64,
+	OPT_IV_FROM_FS,
 	OPT_KDF,
 	OPT_MODE_NUM,
 	OPT_PADDING,
@@ -2004,8 +2019,10 @@ static const struct option longopts[] = {
 	{ "fs-uuid",         required_argument, NULL, OPT_FS_UUID },
 	{ "help",            no_argument,       NULL, OPT_HELP },
 	{ "inode-number",    required_argument, NULL, OPT_INODE_NUMBER },
+	{ "iv",              required_argument, NULL, OPT_IV },
 	{ "iv-ino-lblk-32",  no_argument,       NULL, OPT_IV_INO_LBLK_32 },
 	{ "iv-ino-lblk-64",  no_argument,       NULL, OPT_IV_INO_LBLK_64 },
+	{ "iv-from-fs",      no_argument,       NULL, OPT_IV_FROM_FS },
 	{ "kdf",             required_argument, NULL, OPT_KDF },
 	{ "mode-num",        required_argument, NULL, OPT_MODE_NUM },
 	{ "padding",         required_argument, NULL, OPT_PADDING },
@@ -2082,12 +2099,23 @@ int main(int argc, char *argv[])
 			if (params.inode_number <= 0 || *tmp || errno)
 				die("Invalid inode number: %s", optarg);
 			break;
+		case OPT_IV:
+			int iv_len = hex2bin(optarg, params.iv, MAX_IV_SIZE);
+			if ((iv_len != AES_BLOCK_SIZE) &&
+			    (iv_len != ADIANTUM_IV_SIZE))
+				die("Invalid iv length: %d (must be %u or %u)",
+				    iv_len, AES_BLOCK_SIZE, ADIANTUM_IV_SIZE);
+			params.iv_set = true;
+			break;
 		case OPT_IV_INO_LBLK_32:
 			params.iv_ino_lblk_32 = true;
 			break;
 		case OPT_IV_INO_LBLK_64:
 			params.iv_ino_lblk_64 = true;
 			break;
+		case OPT_IV_FROM_FS:
+			params.iv_from_fs = true;
+			break;
 		case OPT_KDF:
 			params.kdf = parse_kdf_algorithm(optarg);
 			break;
diff --git a/tests/generic/576 b/tests/generic/576
index 7a368a8d..060d5ac8 100755
--- a/tests/generic/576
+++ b/tests/generic/576
@@ -61,6 +61,7 @@ cmp $fsv_orig_file $fsv_file && echo "Files matched"
 
 # Just in case, try again after a mount cycle to empty the page cache.
 _scratch_cycle_mount
+_add_default_policy_key $raw_key &>> $seqres.full
 cmp $fsv_orig_file $fsv_file && echo "Files matched"
 
 # Corrupt some bytes as a sanity check that fs-verity is really working.
