btrfs-progs: receive: fix reading header on strict alignment hosts

There's a report:

ERROR: Failed to send/receive subvolume: .../testbackup.20240330T1102  -> .../testbackup.20240330T1102
ERROR: ... Command execution failed (exitcode=1)
ERROR: ... sh: btrfs send '.../testbackup.20240330T1102' | ssh user@host.lan 'sudo -n btrfs receive '\''...'\'''
ERROR: ... invalid tlv in cmd tlv_type = 816

This is send/receive between arm64 and armv5el hosts, with btrfs-progs
6.2.1. Last known working version is 5.16. This looked like another
custom protocol extension by NAS vendors but this was a false trace and
this is indeed a bug in stream parsing after changes to the v2 protocol.

The most likely explanation is that the armv5 host requires strict
alignment for reads (32bit type must be 4 byte aligned) but the way the
raw data buffer is mapped to the cmd structure in read_cmd() does not
guarantee that.

Issue: #770
Fixes: aa1ca3789e ("btrfs-progs: receive: support v2 send stream DATA tlv format")
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
David Sterba 2024-05-21 15:50:46 +02:00
parent c1d8bd82ca
commit 09a5fe669f

View file

@ -131,9 +131,10 @@ static int read_cmd(struct btrfs_send_stream *sctx)
goto out;
}
/* The read_buf does not guarantee any aligmnet for any structures. */
cmd_hdr = (struct btrfs_cmd_header *)sctx->read_buf;
cmd_len = le32_to_cpu(cmd_hdr->len);
cmd = le16_to_cpu(cmd_hdr->cmd);
cmd_len = get_unaligned_le32(&cmd_hdr->len);
cmd = get_unaligned_le16(&cmd_hdr->cmd);
buf_len = sizeof(*cmd_hdr) + cmd_len;
if (sctx->read_buf_size < buf_len) {
void *new_read_buf;
@ -160,9 +161,9 @@ static int read_cmd(struct btrfs_send_stream *sctx)
goto out;
}
crc = le32_to_cpu(cmd_hdr->crc);
crc = get_unaligned_le32(&cmd_hdr->crc);
/* In send, CRC is computed with header crc = 0, replicate that */
cmd_hdr->crc = 0;
put_unaligned_le32(0, &cmd_hdr->crc);
crc2 = crc32c(0, (unsigned char*)sctx->read_buf,
sizeof(*cmd_hdr) + cmd_len);
@ -183,7 +184,7 @@ static int read_cmd(struct btrfs_send_stream *sctx)
ret = -EINVAL;
goto out;
}
tlv_type = le16_to_cpu(*(__le16 *)data);
tlv_type = get_unaligned_le16(data);
if (tlv_type == 0 || tlv_type > __BTRFS_SEND_A_MAX) {
error("invalid tlv in cmd tlv_type = %hu", tlv_type);
@ -204,7 +205,7 @@ static int read_cmd(struct btrfs_send_stream *sctx)
ret = -EINVAL;
goto out;
}
send_attr->tlv_len = le16_to_cpu(*(__le16 *)data);
send_attr->tlv_len = get_unaligned_le16(data);
pos += sizeof(__le16);
data += sizeof(__le16);
}
@ -322,8 +323,8 @@ static int tlv_get_timespec(struct btrfs_send_stream *sctx,
TLV_GET(sctx, attr, (void**)&bts, &len);
TLV_CHECK_LEN(sizeof(*bts), len);
ts->tv_sec = le64_to_cpu(bts->sec);
ts->tv_nsec = le32_to_cpu(bts->nsec);
ts->tv_sec = get_unaligned_le64(&bts->sec);
ts->tv_nsec = get_unaligned_le32(&bts->nsec);
ret = 0;
tlv_get_failed: