dm verity: add error handling modes for corrupted blocks
[firefly-linux-kernel-4.4.55.git] / drivers / md / dm-verity.c
index 7a7bab8947ae3485d31c132cb3398251c7d507cf..66616db33e6fdbc14896704fe50483930ffb1cfd 100644 (file)
 
 #include <linux/module.h>
 #include <linux/device-mapper.h>
+#include <linux/reboot.h>
 #include <crypto/hash.h>
 
 #define DM_MSG_PREFIX                  "verity"
 
+#define DM_VERITY_ENV_LENGTH           42
+#define DM_VERITY_ENV_VAR_NAME         "DM_VERITY_ERR_BLOCK_NR"
+
 #define DM_VERITY_IO_VEC_INLINE                16
 #define DM_VERITY_MEMPOOL_SIZE         4
 #define DM_VERITY_DEFAULT_PREFETCH_SIZE        262144
 
 #define DM_VERITY_MAX_LEVELS           63
+#define DM_VERITY_MAX_CORRUPTED_ERRS   100
+
+#define DM_VERITY_OPT_LOGGING          "ignore_corruption"
+#define DM_VERITY_OPT_RESTART          "restart_on_corruption"
 
 static unsigned dm_verity_prefetch_cluster = DM_VERITY_DEFAULT_PREFETCH_SIZE;
 
 module_param_named(prefetch_cluster, dm_verity_prefetch_cluster, uint, S_IRUGO | S_IWUSR);
 
+enum verity_mode {
+       DM_VERITY_MODE_EIO,
+       DM_VERITY_MODE_LOGGING,
+       DM_VERITY_MODE_RESTART
+};
+
+enum verity_block_type {
+       DM_VERITY_BLOCK_TYPE_DATA,
+       DM_VERITY_BLOCK_TYPE_METADATA
+};
+
 struct dm_verity {
        struct dm_dev *data_dev;
        struct dm_dev *hash_dev;
@@ -54,6 +73,8 @@ struct dm_verity {
        unsigned digest_size;   /* digest size for the current hash algorithm */
        unsigned shash_descsize;/* the size of temporary space for crypto */
        int hash_failed;        /* set to 1 if hash of any block failed */
+       enum verity_mode mode;  /* mode for handling verification errors */
+       unsigned corrupted_errs;/* Number of errors for corrupted blocks */
 
        mempool_t *vec_mempool; /* mempool of bio vector */
 
@@ -174,6 +195,57 @@ static void verity_hash_at_level(struct dm_verity *v, sector_t block, int level,
                *offset = idx << (v->hash_dev_block_bits - v->hash_per_block_bits);
 }
 
+/*
+ * Handle verification errors.
+ */
+static int verity_handle_err(struct dm_verity *v, enum verity_block_type type,
+                            unsigned long long block)
+{
+       char verity_env[DM_VERITY_ENV_LENGTH];
+       char *envp[] = { verity_env, NULL };
+       const char *type_str = "";
+       struct mapped_device *md = dm_table_get_md(v->ti->table);
+
+       /* Corruption should be visible in device status in all modes */
+       v->hash_failed = 1;
+
+       if (v->corrupted_errs >= DM_VERITY_MAX_CORRUPTED_ERRS)
+               goto out;
+
+       v->corrupted_errs++;
+
+       switch (type) {
+       case DM_VERITY_BLOCK_TYPE_DATA:
+               type_str = "data";
+               break;
+       case DM_VERITY_BLOCK_TYPE_METADATA:
+               type_str = "metadata";
+               break;
+       default:
+               BUG();
+       }
+
+       DMERR("%s: %s block %llu is corrupted", v->data_dev->name, type_str,
+               block);
+
+       if (v->corrupted_errs == DM_VERITY_MAX_CORRUPTED_ERRS)
+               DMERR("%s: reached maximum errors", v->data_dev->name);
+
+       snprintf(verity_env, DM_VERITY_ENV_LENGTH, "%s=%d,%llu",
+               DM_VERITY_ENV_VAR_NAME, type, block);
+
+       kobject_uevent_env(&disk_to_dev(dm_disk(md))->kobj, KOBJ_CHANGE, envp);
+
+out:
+       if (v->mode == DM_VERITY_MODE_LOGGING)
+               return 0;
+
+       if (v->mode == DM_VERITY_MODE_RESTART)
+               kernel_restart("dm-verity device corrupted");
+
+       return 1;
+}
+
 /*
  * Verify hash of a metadata block pertaining to the specified data block
  * ("block" argument) at a specified level ("level" argument).
@@ -251,11 +323,11 @@ static int verity_verify_level(struct dm_verity_io *io, sector_t block,
                        goto release_ret_r;
                }
                if (unlikely(memcmp(result, io_want_digest(v, io), v->digest_size))) {
-                       DMERR_LIMIT("metadata block %llu is corrupted",
-                               (unsigned long long)hash_block);
-                       v->hash_failed = 1;
-                       r = -EIO;
-                       goto release_ret_r;
+                       if (verity_handle_err(v, DM_VERITY_BLOCK_TYPE_METADATA,
+                                             hash_block)) {
+                               r = -EIO;
+                               goto release_ret_r;
+                       }
                } else
                        aux->hash_verified = 1;
        }
@@ -367,10 +439,9 @@ test_block_hash:
                        return r;
                }
                if (unlikely(memcmp(result, io_want_digest(v, io), v->digest_size))) {
-                       DMERR_LIMIT("data block %llu is corrupted",
-                               (unsigned long long)(io->block + b));
-                       v->hash_failed = 1;
-                       return -EIO;
+                       if (verity_handle_err(v, DM_VERITY_BLOCK_TYPE_DATA,
+                                             io->block + b))
+                               return -EIO;
                }
        }
 
@@ -546,6 +617,19 @@ static void verity_status(struct dm_target *ti, status_type_t type,
                else
                        for (x = 0; x < v->salt_size; x++)
                                DMEMIT("%02x", v->salt[x]);
+               if (v->mode != DM_VERITY_MODE_EIO) {
+                       DMEMIT(" 1 ");
+                       switch (v->mode) {
+                       case DM_VERITY_MODE_LOGGING:
+                               DMEMIT(DM_VERITY_OPT_LOGGING);
+                               break;
+                       case DM_VERITY_MODE_RESTART:
+                               DMEMIT(DM_VERITY_OPT_RESTART);
+                               break;
+                       default:
+                               BUG();
+                       }
+               }
                break;
        }
 }
@@ -647,13 +731,19 @@ static void verity_dtr(struct dm_target *ti)
 static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
 {
        struct dm_verity *v;
-       unsigned num;
+       struct dm_arg_set as;
+       const char *opt_string;
+       unsigned int num, opt_params;
        unsigned long long num_ll;
        int r;
        int i;
        sector_t hash_position;
        char dummy;
 
+       static struct dm_arg _args[] = {
+               {0, 1, "Invalid number of feature args"},
+       };
+
        v = kzalloc(sizeof(struct dm_verity), GFP_KERNEL);
        if (!v) {
                ti->error = "Cannot allocate verity structure";
@@ -668,8 +758,8 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                goto bad;
        }
 
-       if (argc != 10) {
-               ti->error = "Invalid argument count: exactly 10 arguments required";
+       if (argc < 10) {
+               ti->error = "Not enough arguments";
                r = -EINVAL;
                goto bad;
        }
@@ -790,6 +880,39 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                }
        }
 
+       argv += 10;
+       argc -= 10;
+
+       /* Optional parameters */
+       if (argc) {
+               as.argc = argc;
+               as.argv = argv;
+
+               r = dm_read_arg_group(_args, &as, &opt_params, &ti->error);
+               if (r)
+                       goto bad;
+
+               while (opt_params) {
+                       opt_params--;
+                       opt_string = dm_shift_arg(&as);
+                       if (!opt_string) {
+                               ti->error = "Not enough feature arguments";
+                               r = -EINVAL;
+                               goto bad;
+                       }
+
+                       if (!strcasecmp(opt_string, DM_VERITY_OPT_LOGGING))
+                               v->mode = DM_VERITY_MODE_LOGGING;
+                       else if (!strcasecmp(opt_string, DM_VERITY_OPT_RESTART))
+                               v->mode = DM_VERITY_MODE_RESTART;
+                       else {
+                               ti->error = "Invalid feature arguments";
+                               r = -EINVAL;
+                               goto bad;
+                       }
+               }
+       }
+
        v->hash_per_block_bits =
                __fls((1 << v->hash_dev_block_bits) / v->digest_size);