Repository: rauc/rauc (C)

Issue:
Adaptive update fails when image is too large
I'm currently trying out the adaptive update feature with ext4 rootfs partitions.
The update works as expected when the ext4 partition in the bundle and the rootfs slot have the same size.
But when the size differs RAUC crashes with `g_propagate_error: assertion 'src != NULL' failed` instead of a meaningful error message. 
A check for a size mismatch could probably prevent this.

```
rauc install /tmp/phytec-headless-bundle-phyboard-segin-imx6ul-7.raucb
installing
 0% Installing
 0% Determining slot states
20% Determining slot states done.
20% Checking bundle
20% Verifying signature
40% Verifying signature done.
40% Checking bundle done.
40% Checking manifest contents
60% Checking manifest contents done.
60% Determining target install group
80% Determining target install group done.
80% Updating slots
80% Checking slot rootfs.0
83% Checking slot rootfs.0 done.
83% Copying image to rootfs.0
RAUC service disappeared
D-Bus error while installing `/tmp/phytec-headless-bundle-phyboard-segin-imx6ul-7.raucb`
```
Output of journalctl -u rauc
```
Apr 28 17:57:40 phyboard-segin-imx6ul-7 systemd[1]: Starting RAUC Update Service...
Apr 28 17:57:40 phyboard-segin-imx6ul-7 rauc[363]: Using central status file /mnt/config/rauc/central.raucs
Apr 28 17:57:40 phyboard-segin-imx6ul-7 rauc[363]: Using central status file /mnt/config/rauc/central.raucs
Apr 28 17:57:40 phyboard-segin-imx6ul-7 systemd[1]: Started RAUC Update Service.
Apr 28 17:57:40 phyboard-segin-imx6ul-7 rauc[363]: input bundle: /tmp/phytec-headless-bundle-phyboard-segin-imx6ul-7.raucb
Apr 28 17:57:40 phyboard-segin-imx6ul-7 rauc[363]: Active slot bootname: system1
Apr 28 17:57:40 phyboard-segin-imx6ul-7 rauc[363]: installing /tmp/phytec-headless-bundle-phyboard-segin-imx6ul-7.raucb: started
Apr 28 17:57:40 phyboard-segin-imx6ul-7 rauc[363]: installing /tmp/phytec-headless-bundle-phyboard-segin-imx6ul-7.raucb: Checking and mounting bundle...
Apr 28 17:57:40 phyboard-segin-imx6ul-7 rauc[363]: Reading bundle: /tmp/phytec-headless-bundle-phyboard-segin-imx6ul-7.raucb
Apr 28 17:57:40 phyboard-segin-imx6ul-7 rauc[363]: Verifying bundle signature... 
Apr 28 17:57:40 phyboard-segin-imx6ul-7 rauc[363]: Verified inline signature by 'O = SW, CN = PHYTEC Messtechnik GmbH Development-1'
Apr 28 17:57:40 phyboard-segin-imx6ul-7 rauc[363]: Mounting bundle '/tmp/phytec-headless-bundle-phyboard-segin-imx6ul-7.raucb' to '/run/rauc/bundle'
Apr 28 17:57:41 phyboard-segin-imx6ul-7 rauc[363]: Configured loop device '/dev/loop0' for 76886016 bytes
Apr 28 17:57:41 phyboard-segin-imx6ul-7 rauc[363]: Configured dm-verity device '/dev/dm-0'
Apr 28 17:57:41 phyboard-segin-imx6ul-7 rauc[363]: Starting pre install handler: /usr/lib/rauc/rauc-pre-install.sh
Apr 28 17:57:41 phyboard-segin-imx6ul-7 rauc[363]: Marking target slot rootfs.0 as non-bootable...
Apr 28 17:57:41 phyboard-segin-imx6ul-7 rauc[363]: installing /tmp/phytec-headless-bundle-phyboard-segin-imx6ul-7.raucb: Updating slots...
Apr 28 17:57:41 phyboard-segin-imx6ul-7 rauc[363]: Checking image type for slot type: ext4
Apr 28 17:57:41 phyboard-segin-imx6ul-7 rauc[363]: Image detected as type: *.ext4
Apr 28 17:57:41 phyboard-segin-imx6ul-7 rauc[363]: installing /tmp/phytec-headless-bundle-phyboard-segin-imx6ul-7.raucb: Checking slot rootfs.0
Apr 28 17:57:41 phyboard-segin-imx6ul-7 rauc[363]: installing /tmp/phytec-headless-bundle-phyboard-segin-imx6ul-7.raucb: Updating slot rootfs.0
Apr 28 17:57:41 phyboard-segin-imx6ul-7 rauc[363]: Updating /dev/mmcblk1p5 with /run/rauc/bundle/phytec-headless-image-phyboard-segin-imx6ul-7.ext4
Apr 28 17:58:01 phyboard-segin-imx6ul-7 rauc[363]: g_propagate_error: assertion 'src != NULL' failed
Apr 28 17:58:01 phyboard-segin-imx6ul-7 systemd[1]: rauc.service: Main process exited, code=killed, status=5/TRAP
Apr 28 17:58:01 phyboard-segin-imx6ul-7 systemd[1]: rauc.service: Failed with result 'signal'.```


Discussion: It should already work if the target slot is larger. Which exact sizes do your image and slots have?
The target slot is 205225984 bytes large and the partition in the bundle is 205565952 bytes in size.
I have tried the larger target with a smaller update which does indeed work.
OK. So the issue here is that we should abort the installation earlier with a useful error message.



Fix (patch):
diff --git a/include/utils.h b/include/utils.h
index b56012759..77591c2fc 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -3,6 +3,15 @@
 #include <gio/gio.h>
 #include <glib.h>
 
+#define R_UTILS_ERROR r_utils_error_quark()
+
+GQuark r_utils_error_quark(void);
+
+typedef enum {
+	R_UTILS_ERROR_FAILED,
+	R_UTILS_ERROR_INAPPROPRIATE_IOCTL,
+} RUtilsError;
+
 #define BIT(nr) (1UL << (nr))
 
 /* Evaluate EXPRESSION, and repeat as long as it returns -1 with `errno'
@@ -223,3 +232,6 @@ G_GNUC_WARN_UNUSED_RESULT;
 
 guint get_sectorsize(gint fd)
 G_GNUC_WARN_UNUSED_RESULT;
+
+goffset get_device_size(gint fd, GError **error)
+G_GNUC_WARN_UNUSED_RESULT;
diff --git a/src/update_handler.c b/src/update_handler.c
index 188f0c517..82cf22b61 100644
--- a/src/update_handler.c
+++ b/src/update_handler.c
@@ -30,6 +30,34 @@ GQuark r_update_error_quark(void)
 	return g_quark_from_static_string("r_update_error_quark");
 }
 
+static gboolean check_image_size(int fd, const RaucImage *image, GError **error)
+{
+	GError *ierror = NULL;
+	goffset dev_size;
+
+	g_return_val_if_fail(image, FALSE);
+	g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
+
+	dev_size = get_device_size(fd, &ierror);
+	if (g_error_matches(ierror, R_UTILS_ERROR, R_UTILS_ERROR_INAPPROPRIATE_IOCTL)) {
+		g_info("Slot is not a block device, skipping size check");
+		return TRUE;
+	}
+
+	if (dev_size < image->checksum.size) {
+		if (ierror) {
+			g_propagate_error(error, ierror);
+		} else {
+			g_set_error(error, R_UPDATE_ERROR, R_UPDATE_ERROR_FAILED,
+					"Slot (%"G_GOFFSET_FORMAT " bytes) is too small for image (%"G_GOFFSET_FORMAT " bytes).",
+					dev_size, image->checksum.size);
+		}
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 /* the fd will only live as long as the returned output stream */
 static GUnixOutputStream* open_slot_device(RaucSlot *slot, int *fd, GError **error)
 {
@@ -548,6 +576,13 @@ static gboolean copy_raw_image_to_dev(RaucImage *image, RaucSlot *slot, GError *
 		goto out;
 	}
 
+	/* check size */
+	if (!check_image_size(g_unix_output_stream_get_fd(outstream), image, &ierror)) {
+		res = FALSE;
+		g_propagate_error(error, ierror);
+		goto out;
+	}
+
 	/* copy */
 	g_message("writing data to device %s", slot->device);
 	res = copy_raw_image(image, outstream, 0, &ierror);
@@ -596,6 +631,12 @@ static gboolean copy_block_hash_index_image_to_dev(RaucImage *image, RaucSlot *s
 		res = FALSE;
 		goto out;
 	}
+	if (!check_image_size(tmp->data_fd, image, &ierror)) {
+		g_propagate_error(error, ierror);
+		res = FALSE;
+		goto out;
+	}
+
 	g_ptr_array_add(sources, g_steal_pointer(&tmp));
 
 	/* Open and append seed slot. */
@@ -773,7 +814,7 @@ static gboolean copy_adaptive_image_to_dev(RaucImage *image, RaucSlot *slot, GEr
 	g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
 
 	if (g_strv_contains((const gchar * const*)image->adaptive, "block-hash-index")) {
-		g_info("Selected adaptive update method 'block-hash-index");
+		g_info("Selected adaptive update method 'block-hash-index'");
 
 		if (!copy_block_hash_index_image_to_dev(image, slot, &ierror)) {
 			g_propagate_error(error, ierror);
diff --git a/src/utils.c b/src/utils.c
index e55df3a83..fbe5c2115 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -12,6 +12,11 @@
 
 #include "utils.h"
 
+GQuark r_utils_error_quark(void)
+{
+	return g_quark_from_static_string("r_utils_error_quark");
+}
+
 GSubprocess *r_subprocess_new(GSubprocessFlags flags, GError **error, const gchar *argv0, ...)
 {
 	GSubprocess *result;
@@ -499,3 +504,30 @@ guint get_sectorsize(gint fd)
 
 	return sector_size;
 }
+
+goffset get_device_size(gint fd, GError **error)
+{
+	guint64 size = 0;
+
+	g_return_val_if_fail(error == NULL || *error == NULL, 0);
+
+	if (ioctl(fd, BLKGETSIZE64, &size) != 0) {
+		int err = errno;
+		if (err == ENOTTY) {
+			g_set_error(error,
+					R_UTILS_ERROR,
+					R_UTILS_ERROR_INAPPROPRIATE_IOCTL,
+					"Failed to get device size: Not a block device");
+		} else {
+			g_set_error(error,
+					G_FILE_ERROR,
+					g_file_error_from_errno(err),
+					"Failed to get device size: %s", g_strerror(err));
+		}
+		return 0;
+	}
+
+	g_assert(size <= G_MAXOFFSET);
+
+	return size;
+}