[PATCH 3/5] zfs: Fix unaligned read of uint64

mwleeds at mailtundra.com mwleeds at mailtundra.com
Sun Apr 7 03:47:27 CEST 2024


Without this patch, when trying to boot zfs using U-Boot on a Jetson TX2
NX (which is aarch64), I get a CPU reset error like so:

"Synchronous Abort" handler, esr 0x96000021
elr: 00000000800c9000 lr : 00000000800c8ffc (reloc)
elr: 00000000fff77000 lr : 00000000fff76ffc
x0 : 00000000ffb40f04 x1 : 0000000000000000
x2 : 000000000000000a x3 : 0000000003100000
x4 : 0000000003100000 x5 : 0000000000000034
x6 : 00000000fff9cc6e x7 : 000000000000000f
x8 : 00000000ff7f84a0 x9 : 0000000000000008
x10: 00000000ffb40f04 x11: 0000000000000006
x12: 000000000001869f x13: 0000000000000001
x14: 00000000ff7f84bc x15: 0000000000000010
x16: 0000000000002080 x17: 00000000001fffff
x18: 00000000ff7fbdd8 x19: 00000000ffb405f8
x20: 00000000ffb40dd0 x21: 00000000fffabe5e
x22: 000000ea77940000 x23: 00000000ffb42090
x24: 0000000000000000 x25: 0000000000000000
x26: 0000000000000000 x27: 0000000000000000
x28: 0000000000bab10c x29: 00000000ff7f85f0

Code: d00001a0 9103a000 94006ac6 f9401ba0 (f9400000)
Resetting CPU ...

This happens when be64_to_cpu() is called on a value that exists at a
memory address that's 4 byte aligned but not 8 byte aligned (e.g. an
address ending in 04). The call stack where that happens is:
check_pool_label() ->
zfs_nvlist_lookup_uint64(vdevnvlist, ZPOOL_CONFIG_ASHIFT,...) ->
be64_to_cpu()

Signed-off-by: Phaedrus Leeds <mwleeds at mailtundra.com>
Tested-by: Phaedrus Leeds <mwleeds at mailtundra.com>
---
 fs/zfs/zfs.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/fs/zfs/zfs.c b/fs/zfs/zfs.c
index 61d58fce68..9a50deac18 100644
--- a/fs/zfs/zfs.c
+++ b/fs/zfs/zfs.c
@@ -1552,35 +1552,53 @@ nvlist_find_value(char *nvlist, char *name, int valtype, char **val,
 			if (nelm_out)
 				*nelm_out = nelm;
 			return 1;
 		}
 
 		nvlist += encode_size;	/* goto the next nvpair */
 	}
 	return 0;
 }
 
+int is_word_aligned_ptr(void *ptr) {
+	return ((uintptr_t)ptr & (sizeof(void *) - 1)) == 0;
+}
+
 int
 zfs_nvlist_lookup_uint64(char *nvlist, char *name, uint64_t *out)
 {
 	char *nvpair;
 	size_t size;
 	int found;
 
 	found = nvlist_find_value(nvlist, name, DATA_TYPE_UINT64, &nvpair, &size, 0);
 	if (!found)
 		return 0;
 	if (size < sizeof(uint64_t)) {
 		printf("invalid uint64\n");
 		return ZFS_ERR_BAD_FS;
 	}
 
+	/* On arm64, calling be64_to_cpu() on a value stored at a memory address
+	 * that's not 8-byte aligned causes the CPU to reset. Avoid that by copying the
+	 * value somewhere else if needed.
+	 */
+	if (!is_word_aligned_ptr((void *)nvpair)) {
+		uint64_t *alignedptr = malloc(sizeof(uint64_t));
+		if (!alignedptr)
+			return 0;
+		memcpy(alignedptr, nvpair, sizeof(uint64_t));
+		*out = be64_to_cpu(*alignedptr);
+		free(alignedptr);
+		return 1;
+	}
+
 	*out = be64_to_cpu(*(uint64_t *) nvpair);
 	return 1;
 }
 
 char *
 zfs_nvlist_lookup_string(char *nvlist, char *name)
 {
 	char *nvpair;
 	char *ret;
 	size_t slen;
-- 
2.44.0



More information about the U-Boot mailing list