am c28e2598: am 3fdc2f2b: am d33c6c0e: Merge "Use structured format for verity metadata"

* commit 'c28e2598af394834dbf6532f1cf252705fabd76b':
  Use structured format for verity metadata
diff --git a/fs_mgr/fs_mgr_fstab.c b/fs_mgr/fs_mgr_fstab.c
index 64f7edc..97cf811 100644
--- a/fs_mgr/fs_mgr_fstab.c
+++ b/fs_mgr/fs_mgr_fstab.c
@@ -24,6 +24,7 @@
 
 struct fs_mgr_flag_values {
     char *key_loc;
+    char *verity_loc;
     long long part_length;
     char *label;
     int partnum;
@@ -108,6 +109,14 @@
                      * location of the keys.  Get it and return it.
                      */
                     flag_vals->key_loc = strdup(strchr(p, '=') + 1);
+                } else if ((fl[i].flag == MF_VERIFY) && flag_vals) {
+                    /* If the verify flag is followed by an = and the
+                     * location for the verity state,  get it and return it.
+                     */
+                    char *start = strchr(p, '=');
+                    if (start) {
+                        flag_vals->verity_loc = strdup(start + 1);
+                    }
                 } else if ((fl[i].flag == MF_FORCECRYPT) && flag_vals) {
                     /* The forceencrypt flag is followed by an = and the
                      * location of the keys.  Get it and return it.
@@ -292,6 +301,7 @@
         fstab->recs[cnt].fs_mgr_flags = parse_flags(p, fs_mgr_flags,
                                                     &flag_vals, NULL, 0);
         fstab->recs[cnt].key_loc = flag_vals.key_loc;
+        fstab->recs[cnt].verity_loc = flag_vals.verity_loc;
         fstab->recs[cnt].length = flag_vals.part_length;
         fstab->recs[cnt].label = flag_vals.label;
         fstab->recs[cnt].partnum = flag_vals.partnum;
diff --git a/fs_mgr/fs_mgr_verity.c b/fs_mgr/fs_mgr_verity.c
index 5c67333..0d5357b 100644
--- a/fs_mgr/fs_mgr_verity.c
+++ b/fs_mgr/fs_mgr_verity.c
@@ -47,16 +47,23 @@
 #define VERITY_METADATA_SIZE 32768
 #define VERITY_TABLE_RSA_KEY "/verity_key"
 
+#define METADATA_MAGIC 0x01564c54
+#define METADATA_TAG_MAX_LENGTH 63
+#define METADATA_EOD "eod"
+
+#define VERITY_STATE_TAG "verity_state"
 #define VERITY_STATE_HEADER 0x83c0ae9d
 #define VERITY_STATE_VERSION 1
 
 #define VERITY_KMSG_RESTART "dm-verity device corrupted"
 #define VERITY_KMSG_BUFSIZE 1024
 
+#define __STRINGIFY(x) #x
+#define STRINGIFY(x) __STRINGIFY(x)
+
 struct verity_state {
     uint32_t header;
     uint32_t version;
-    uint32_t size;
     int32_t mode;
 };
 
@@ -453,17 +460,117 @@
     return 0;
 }
 
+static int metadata_add(FILE *fp, long start, const char *tag,
+        unsigned int length, off64_t *offset)
+{
+    if (TEMP_FAILURE_RETRY(fseek(fp, start, SEEK_SET)) < 0 ||
+        TEMP_FAILURE_RETRY(fprintf(fp, "%s %u\n", tag, length)) < 0) {
+        return -1;
+    }
+
+    *offset = ftell(fp);
+
+    if (TEMP_FAILURE_RETRY(fseek(fp, length, SEEK_CUR)) < 0 ||
+        TEMP_FAILURE_RETRY(fprintf(fp, METADATA_EOD " 0\n")) < 0) {
+        return -1;
+    }
+
+    return 0;
+}
+
+static int metadata_find(const char *fname, const char *stag,
+        unsigned int slength, off64_t *offset)
+{
+    FILE *fp = NULL;
+    char tag[METADATA_TAG_MAX_LENGTH + 1];
+    int rc = -1;
+    int n;
+    long start = 0x4000; /* skip cryptfs metadata area */
+    uint32_t magic;
+    unsigned int length = 0;
+
+    if (!fname) {
+        return -1;
+    }
+
+    fp = TEMP_FAILURE_RETRY(fopen(fname, "r+"));
+
+    if (!fp) {
+        ERROR("Failed to open %s (%s)\n", fname, strerror(errno));
+        goto out;
+    }
+
+    /* check magic */
+    if (TEMP_FAILURE_RETRY(fseek(fp, start, SEEK_SET)) < 0 ||
+        TEMP_FAILURE_RETRY(fread(&magic, sizeof(magic), 1, fp)) != 1) {
+        ERROR("Failed to read magic from %s (%s)\n", fname, strerror(errno));
+        goto out;
+    }
+
+    if (magic != METADATA_MAGIC) {
+        magic = METADATA_MAGIC;
+
+        if (TEMP_FAILURE_RETRY(fseek(fp, start, SEEK_SET)) < 0 ||
+            TEMP_FAILURE_RETRY(fwrite(&magic, sizeof(magic), 1, fp)) != 1) {
+            ERROR("Failed to write magic to %s (%s)\n", fname, strerror(errno));
+            goto out;
+        }
+
+        rc = metadata_add(fp, start + sizeof(magic), stag, slength, offset);
+        if (rc < 0) {
+            ERROR("Failed to add metadata to %s: %s\n", fname, strerror(errno));
+        }
+
+        goto out;
+    }
+
+    start += sizeof(magic);
+
+    while (1) {
+        n = TEMP_FAILURE_RETRY(fscanf(fp,
+                "%" STRINGIFY(METADATA_TAG_MAX_LENGTH) "s %u\n",
+                tag, &length));
+
+        if (n == 2 && strcmp(tag, METADATA_EOD)) {
+            /* found a tag */
+            start = ftell(fp);
+
+            if (!strcmp(tag, stag) && length == slength) {
+                *offset = start;
+                rc = 0;
+                goto out;
+            }
+
+            start += length;
+
+            if (TEMP_FAILURE_RETRY(fseek(fp, length, SEEK_CUR)) < 0) {
+                ERROR("Failed to seek %s (%s)\n", fname, strerror(errno));
+                goto out;
+            }
+        } else {
+            rc = metadata_add(fp, start, stag, slength, offset);
+            if (rc < 0) {
+                ERROR("Failed to write metadata to %s: %s\n", fname,
+                    strerror(errno));
+            }
+            goto out;
+        }
+   }
+
+out:
+    if (fp) {
+        TEMP_FAILURE_RETRY(fflush(fp));
+        TEMP_FAILURE_RETRY(fclose(fp));
+    }
+
+    return rc;
+}
+
 static int write_verity_state(const char *fname, off64_t offset, int32_t mode)
 {
     int fd;
     int rc = -1;
-
-    struct verity_state s = {
-        VERITY_STATE_HEADER,
-        VERITY_STATE_VERSION,
-        sizeof(struct verity_state),
-        mode
-    };
+    struct verity_state s = { VERITY_STATE_HEADER, VERITY_STATE_VERSION, mode };
 
     fd = TEMP_FAILURE_RETRY(open(fname, O_WRONLY | O_SYNC | O_CLOEXEC));
 
@@ -472,13 +579,9 @@
         goto out;
     }
 
-    if (TEMP_FAILURE_RETRY(lseek64(fd, offset, SEEK_SET)) < 0) {
-        ERROR("Failed to seek %s to %" PRIu64 " (%s)\n", fname, offset, strerror(errno));
-        goto out;
-    }
-
-    if (TEMP_FAILURE_RETRY(write(fd, &s, sizeof(s))) != sizeof(s)) {
-        ERROR("Failed to write %zu bytes to %s (%s)\n", sizeof(s), fname, strerror(errno));
+    if (TEMP_FAILURE_RETRY(pwrite64(fd, &s, sizeof(s), offset)) != sizeof(s)) {
+        ERROR("Failed to write %zu bytes to %s to offset %" PRIu64 " (%s)\n",
+            sizeof(s), fname, offset, strerror(errno));
         goto out;
     }
 
@@ -492,44 +595,16 @@
     return rc;
 }
 
-static int get_verity_state_location(char *location, off64_t *offset)
+static int load_verity_state(struct fstab_rec *fstab, int *mode)
 {
-    char state_off[PROPERTY_VALUE_MAX];
-
-    if (property_get("ro.verity.state.location", location, NULL) <= 0) {
-        return -1;
-    }
-
-    if (*location != '/' || access(location, R_OK | W_OK) == -1) {
-        ERROR("Failed to access verity state %s (%s)\n", location, strerror(errno));
-        return -1;
-    }
-
-    *offset = 0;
-
-    if (property_get("ro.verity.state.offset", state_off, NULL) > 0) {
-        *offset = strtoll(state_off, NULL, 0);
-
-        if (errno == ERANGE || errno == EINVAL) {
-            ERROR("Invalid value in ro.verity.state.offset (%s)\n", state_off);
-            return -1;
-        }
-    }
-
-    return 0;
-}
-
-int fs_mgr_load_verity_state(int *mode)
-{
-    char fname[PROPERTY_VALUE_MAX];
     int fd = -1;
     int rc = -1;
     off64_t offset = 0;
     struct verity_state s;
 
-    if (get_verity_state_location(fname, &offset) < 0) {
-        /* location for dm-verity state is not specified, fall back to
-         * default behavior: return -EIO for corrupted blocks */
+    if (metadata_find(fstab->verity_loc, VERITY_STATE_TAG, sizeof(s),
+            &offset) < 0) {
+        /* fall back to stateless behavior */
         *mode = VERITY_MODE_EIO;
         rc = 0;
         goto out;
@@ -539,28 +614,27 @@
         /* device was restarted after dm-verity detected a corrupted
          * block, so switch to logging mode */
         *mode = VERITY_MODE_LOGGING;
-        rc = write_verity_state(fname, offset, *mode);
+        rc = write_verity_state(fstab->verity_loc, offset, *mode);
         goto out;
     }
 
-    fd = TEMP_FAILURE_RETRY(open(fname, O_RDONLY | O_CLOEXEC));
+    fd = TEMP_FAILURE_RETRY(open(fstab->verity_loc, O_RDONLY | O_CLOEXEC));
 
     if (fd == -1) {
-        ERROR("Failed to open %s (%s)\n", fname, strerror(errno));
+        ERROR("Failed to open %s (%s)\n", fstab->verity_loc, strerror(errno));
         goto out;
     }
 
-    if (TEMP_FAILURE_RETRY(lseek64(fd, offset, SEEK_SET)) < 0) {
-        ERROR("Failed to seek %s to %" PRIu64 " (%s)\n", fname, offset, strerror(errno));
-        goto out;
-    }
-
-    if (TEMP_FAILURE_RETRY(read(fd, &s, sizeof(s))) != sizeof(s)) {
-        ERROR("Failed to read %zu bytes from %s (%s)\n", sizeof(s), fname, strerror(errno));
+    if (TEMP_FAILURE_RETRY(pread64(fd, &s, sizeof(s), offset)) != sizeof(s)) {
+        ERROR("Failed to read %zu bytes from %s offset %" PRIu64 " (%s)\n",
+            sizeof(s), fstab->verity_loc, offset, strerror(errno));
         goto out;
     }
 
     if (s.header != VERITY_STATE_HEADER) {
+        /* space allocated, but no state written. write default state */
+        *mode = VERITY_MODE_DEFAULT;
+        rc = write_verity_state(fstab->verity_loc, offset, *mode);
         goto out;
     }
 
@@ -569,11 +643,6 @@
         goto out;
     }
 
-    if (s.size != sizeof(s)) {
-        ERROR("Unexpected verity state size (%u)\n", s.size);
-        goto out;
-    }
-
     if (s.mode < VERITY_MODE_EIO ||
         s.mode > VERITY_MODE_LAST) {
         ERROR("Unsupported verity mode (%u)\n", s.mode);
@@ -591,13 +660,61 @@
     return rc;
 }
 
+int fs_mgr_load_verity_state(int *mode)
+{
+    char fstab_filename[PROPERTY_VALUE_MAX + sizeof(FSTAB_PREFIX)];
+    char propbuf[PROPERTY_VALUE_MAX];
+    int rc = -1;
+    int i;
+    struct fstab *fstab = NULL;
+
+    *mode = VERITY_MODE_DEFAULT;
+
+    property_get("ro.hardware", propbuf, "");
+    snprintf(fstab_filename, sizeof(fstab_filename), FSTAB_PREFIX"%s", propbuf);
+
+    fstab = fs_mgr_read_fstab(fstab_filename);
+
+    if (!fstab) {
+        ERROR("Failed to read %s\n", fstab_filename);
+        goto out;
+    }
+
+    for (i = 0; i < fstab->num_entries; i++) {
+        if (!fs_mgr_is_verified(&fstab->recs[i])) {
+            continue;
+        }
+
+        rc = load_verity_state(&fstab->recs[i], mode);
+        if (rc < 0) {
+            continue;
+        }
+
+        /* if any of the verified partitions are in logging mode, return */
+        if (*mode == VERITY_MODE_LOGGING) {
+            rc = 0;
+            goto out;
+        }
+    }
+
+    /* if there were multiple partitions, all in non-logging mode, return the
+     * state of the last one */
+    rc = 0;
+
+out:
+    if (fstab) {
+        fs_mgr_free_fstab(fstab);
+    }
+
+    return rc;
+}
+
 int fs_mgr_update_verity_state(fs_mgr_verity_state_callback callback)
 {
     _Alignas(struct dm_ioctl) char buffer[DM_BUF_SIZE];
     char fstab_filename[PROPERTY_VALUE_MAX + sizeof(FSTAB_PREFIX)];
     char *mount_point;
     char propbuf[PROPERTY_VALUE_MAX];
-    char state_loc[PROPERTY_VALUE_MAX];
     char *status;
     int fd = -1;
     int i;
@@ -606,10 +723,6 @@
     struct dm_ioctl *io = (struct dm_ioctl *) buffer;
     struct fstab *fstab = NULL;
 
-    if (get_verity_state_location(state_loc, &offset) < 0) {
-        goto out;
-    }
-
     fd = TEMP_FAILURE_RETRY(open("/dev/device-mapper", O_RDWR | O_CLOEXEC));
 
     if (fd == -1) {
@@ -632,6 +745,11 @@
             continue;
         }
 
+        if (metadata_find(fstab->recs[i].verity_loc, VERITY_STATE_TAG,
+                sizeof(struct verity_state), &offset) < 0) {
+            continue;
+        }
+
         mount_point = basename(fstab->recs[i].mount_point);
         verity_ioctl_init(io, mount_point, 0);
 
@@ -644,7 +762,8 @@
         status = &buffer[io->data_start + sizeof(struct dm_target_spec)];
 
         if (*status == 'C') {
-            rc = write_verity_state(state_loc, offset, VERITY_MODE_LOGGING);
+            rc = write_verity_state(fstab->recs[i].verity_loc, offset,
+                    VERITY_MODE_LOGGING);
 
             if (rc == -1) {
                 goto out;
@@ -732,8 +851,12 @@
         goto out;
     }
 
-    if (fs_mgr_load_verity_state(&mode) < 0) {
-        mode = VERITY_MODE_RESTART; /* default dm-verity mode */
+    if (load_verity_state(fstab, &mode) < 0) {
+        /* if accessing or updating the state failed, switch to the default
+         * safe mode. This makes sure the device won't end up in an endless
+         * restart loop, and no corrupted data will be exposed to userspace
+         * without a warning. */
+        mode = VERITY_MODE_EIO;
     }
 
     INFO("Enabling dm-verity for %s (mode %d)\n",  mount_point, mode);
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 1af16c2..2655100 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -36,7 +36,8 @@
     VERITY_MODE_EIO = 0,
     VERITY_MODE_LOGGING = 1,
     VERITY_MODE_RESTART = 2,
-    VERITY_MODE_LAST = VERITY_MODE_RESTART
+    VERITY_MODE_LAST = VERITY_MODE_RESTART,
+    VERITY_MODE_DEFAULT = VERITY_MODE_RESTART
 };
 
 /*