Skip to content

Commit 54f0391

Browse files
committed
ima: permit fsverity's file digests in the IMA measurement list
Permit fsverity's file digest (a hash of struct fsverity_descriptor) to be included in the IMA measurement list, based on the new measurement policy rule 'digest_type=verity' option. To differentiate between a regular IMA file hash from an fsverity's file digest, use the new d-ngv2 format field included in the ima-ngv2 template. The following policy rule requires fsverity file digests and specifies the new 'ima-ngv2' template, which contains the new 'd-ngv2' field. The policy rule may be constrained, for example based on a fsuuid or LSM label. measure func=FILE_CHECK digest_type=verity template=ima-ngv2 Acked-by: Stefan Berger <stefanb@linux.ibm.com> Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
1 parent 989dc72 commit 54f0391

File tree

7 files changed

+103
-11
lines changed

7 files changed

+103
-11
lines changed

Documentation/ABI/testing/ima_policy

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ Description:
2727
[fowner=] [fgroup=]]
2828
lsm: [[subj_user=] [subj_role=] [subj_type=]
2929
[obj_user=] [obj_role=] [obj_type=]]
30-
option: [[appraise_type=]] [template=] [permit_directio]
31-
[appraise_flag=] [appraise_algos=] [keyrings=]
30+
option: [digest_type=] [template=] [permit_directio]
31+
[appraise_type=] [appraise_flag=]
32+
[appraise_algos=] [keyrings=]
3233
base:
3334
func:= [BPRM_CHECK][MMAP_CHECK][CREDS_CHECK][FILE_CHECK][MODULE_CHECK]
3435
[FIRMWARE_CHECK]
@@ -51,6 +52,9 @@ Description:
5152
appraise_flag:= [check_blacklist]
5253
Currently, blacklist check is only for files signed with appended
5354
signature.
55+
digest_type:= verity
56+
Require fs-verity's file digest instead of the
57+
regular IMA file hash.
5458
keyrings:= list of keyrings
5559
(eg, .builtin_trusted_keys|.ima). Only valid
5660
when action is "measure" and func is KEY_CHECK.
@@ -149,3 +153,9 @@ Description:
149153
security.ima xattr of a file:
150154

151155
appraise func=SETXATTR_CHECK appraise_algos=sha256,sha384,sha512
156+
157+
Example of a 'measure' rule requiring fs-verity's digests
158+
with indication of type of digest in the measurement list.
159+
160+
measure func=FILE_CHECK digest_type=verity \
161+
template=ima-ngv2

Documentation/security/IMA-templates.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ descriptors by adding their identifier to the format string
6767
- 'n': the name of the event (i.e. the file name), with size up to 255 bytes;
6868
- 'd-ng': the digest of the event, calculated with an arbitrary hash
6969
algorithm (field format: <hash algo>:digest);
70-
- 'd-ngv2': same as d-ng, but prefixed with the "ima" digest type
70+
- 'd-ngv2': same as d-ng, but prefixed with the "ima" or "verity" digest type
7171
(field format: <digest type>:<hash algo>:digest);
7272
- 'd-modsig': the digest of the event without the appended modsig;
7373
- 'n-ng': the name of the event, without size limitations;

security/integrity/ima/ima_api.c

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <linux/xattr.h>
1515
#include <linux/evm.h>
1616
#include <linux/iversion.h>
17+
#include <linux/fsverity.h>
1718

1819
#include "ima.h"
1920

@@ -200,6 +201,32 @@ int ima_get_action(struct user_namespace *mnt_userns, struct inode *inode,
200201
allowed_algos);
201202
}
202203

204+
static int ima_get_verity_digest(struct integrity_iint_cache *iint,
205+
struct ima_max_digest_data *hash)
206+
{
207+
enum hash_algo verity_alg;
208+
int ret;
209+
210+
/*
211+
* On failure, 'measure' policy rules will result in a file data
212+
* hash containing 0's.
213+
*/
214+
ret = fsverity_get_digest(iint->inode, hash->digest, &verity_alg);
215+
if (ret)
216+
return ret;
217+
218+
/*
219+
* Unlike in the case of actually calculating the file hash, in
220+
* the fsverity case regardless of the hash algorithm, return
221+
* the verity digest to be included in the measurement list. A
222+
* mismatch between the verity algorithm and the xattr signature
223+
* algorithm, if one exists, will be detected later.
224+
*/
225+
hash->hdr.algo = verity_alg;
226+
hash->hdr.length = hash_digest_size[verity_alg];
227+
return 0;
228+
}
229+
203230
/*
204231
* ima_collect_measurement - collect file measurement
205232
*
@@ -242,16 +269,30 @@ int ima_collect_measurement(struct integrity_iint_cache *iint,
242269
*/
243270
i_version = inode_query_iversion(inode);
244271
hash.hdr.algo = algo;
272+
hash.hdr.length = hash_digest_size[algo];
245273

246274
/* Initialize hash digest to 0's in case of failure */
247275
memset(&hash.digest, 0, sizeof(hash.digest));
248276

249-
if (buf)
277+
if (iint->flags & IMA_VERITY_REQUIRED) {
278+
result = ima_get_verity_digest(iint, &hash);
279+
switch (result) {
280+
case 0:
281+
break;
282+
case -ENODATA:
283+
audit_cause = "no-verity-digest";
284+
break;
285+
default:
286+
audit_cause = "invalid-verity-digest";
287+
break;
288+
}
289+
} else if (buf) {
250290
result = ima_calc_buffer_hash(buf, size, &hash.hdr);
251-
else
291+
} else {
252292
result = ima_calc_file_hash(file, &hash.hdr);
293+
}
253294

254-
if (result && result != -EBADF && result != -EINVAL)
295+
if (result == -ENOMEM)
255296
goto out;
256297

257298
length = sizeof(hash.hdr) + hash.hdr.length;

security/integrity/ima/ima_main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ static int process_measurement(struct file *file, const struct cred *cred,
335335
hash_algo = ima_get_hash_algo(xattr_value, xattr_len);
336336

337337
rc = ima_collect_measurement(iint, file, buf, size, hash_algo, modsig);
338-
if (rc != 0 && rc != -EBADF && rc != -EINVAL)
338+
if (rc == -ENOMEM)
339339
goto out_locked;
340340

341341
if (!pathbuf) /* ima_rdwr_violation possibly pre-fetched */

security/integrity/ima/ima_policy.c

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,7 @@ enum policy_opt {
10231023
Opt_fowner_gt, Opt_fgroup_gt,
10241024
Opt_uid_lt, Opt_euid_lt, Opt_gid_lt, Opt_egid_lt,
10251025
Opt_fowner_lt, Opt_fgroup_lt,
1026+
Opt_digest_type,
10261027
Opt_appraise_type, Opt_appraise_flag, Opt_appraise_algos,
10271028
Opt_permit_directio, Opt_pcr, Opt_template, Opt_keyrings,
10281029
Opt_label, Opt_err
@@ -1065,6 +1066,7 @@ static const match_table_t policy_tokens = {
10651066
{Opt_egid_lt, "egid<%s"},
10661067
{Opt_fowner_lt, "fowner<%s"},
10671068
{Opt_fgroup_lt, "fgroup<%s"},
1069+
{Opt_digest_type, "digest_type=%s"},
10681070
{Opt_appraise_type, "appraise_type=%s"},
10691071
{Opt_appraise_flag, "appraise_flag=%s"},
10701072
{Opt_appraise_algos, "appraise_algos=%s"},
@@ -1172,6 +1174,21 @@ static void check_template_modsig(const struct ima_template_desc *template)
11721174
#undef MSG
11731175
}
11741176

1177+
/*
1178+
* Warn if the template does not contain the given field.
1179+
*/
1180+
static void check_template_field(const struct ima_template_desc *template,
1181+
const char *field, const char *msg)
1182+
{
1183+
int i;
1184+
1185+
for (i = 0; i < template->num_fields; i++)
1186+
if (!strcmp(template->fields[i]->field_id, field))
1187+
return;
1188+
1189+
pr_notice_once("%s", msg);
1190+
}
1191+
11751192
static bool ima_validate_rule(struct ima_rule_entry *entry)
11761193
{
11771194
/* Ensure that the action is set and is compatible with the flags */
@@ -1214,7 +1231,8 @@ static bool ima_validate_rule(struct ima_rule_entry *entry)
12141231
IMA_INMASK | IMA_EUID | IMA_PCR |
12151232
IMA_FSNAME | IMA_GID | IMA_EGID |
12161233
IMA_FGROUP | IMA_DIGSIG_REQUIRED |
1217-
IMA_PERMIT_DIRECTIO | IMA_VALIDATE_ALGOS))
1234+
IMA_PERMIT_DIRECTIO | IMA_VALIDATE_ALGOS |
1235+
IMA_VERITY_REQUIRED))
12181236
return false;
12191237

12201238
break;
@@ -1707,6 +1725,13 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
17071725
LSM_SUBJ_TYPE,
17081726
AUDIT_SUBJ_TYPE);
17091727
break;
1728+
case Opt_digest_type:
1729+
ima_log_string(ab, "digest_type", args[0].from);
1730+
if ((strcmp(args[0].from, "verity")) == 0)
1731+
entry->flags |= IMA_VERITY_REQUIRED;
1732+
else
1733+
result = -EINVAL;
1734+
break;
17101735
case Opt_appraise_type:
17111736
ima_log_string(ab, "appraise_type", args[0].from);
17121737
if ((strcmp(args[0].from, "imasig")) == 0)
@@ -1797,6 +1822,15 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
17971822
check_template_modsig(template_desc);
17981823
}
17991824

1825+
/* d-ngv2 template field recommended for unsigned fs-verity digests */
1826+
if (!result && entry->action == MEASURE &&
1827+
entry->flags & IMA_VERITY_REQUIRED) {
1828+
template_desc = entry->template ? entry->template :
1829+
ima_template_desc_current();
1830+
check_template_field(template_desc, "d-ngv2",
1831+
"verity rules should include d-ngv2");
1832+
}
1833+
18001834
audit_log_format(ab, "res=%d", !result);
18011835
audit_log_end(ab);
18021836
return result;
@@ -2154,6 +2188,8 @@ int ima_policy_show(struct seq_file *m, void *v)
21542188
else
21552189
seq_puts(m, "appraise_type=imasig ");
21562190
}
2191+
if (entry->flags & IMA_VERITY_REQUIRED)
2192+
seq_puts(m, "digest_type=verity ");
21572193
if (entry->flags & IMA_CHECK_BLACKLIST)
21582194
seq_puts(m, "appraise_flag=check_blacklist ");
21592195
if (entry->flags & IMA_PERMIT_DIRECTIO)

security/integrity/ima/ima_template_lib.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ enum data_formats {
3232

3333
enum digest_type {
3434
DIGEST_TYPE_IMA,
35+
DIGEST_TYPE_VERITY,
3536
DIGEST_TYPE__LAST
3637
};
3738

38-
#define DIGEST_TYPE_NAME_LEN_MAX 4 /* including NUL */
39+
#define DIGEST_TYPE_NAME_LEN_MAX 7 /* including NUL */
3940
static const char * const digest_type_name[DIGEST_TYPE__LAST] = {
40-
[DIGEST_TYPE_IMA] = "ima"
41+
[DIGEST_TYPE_IMA] = "ima",
42+
[DIGEST_TYPE_VERITY] = "verity"
4143
};
4244

4345
static int ima_write_template_field_data(const void *data, const u32 datalen,
@@ -297,7 +299,7 @@ static int ima_eventdigest_init_common(const u8 *digest, u32 digestsize,
297299
*
298300
* where 'DATA_FMT_DIGEST' is the original digest format ('d')
299301
* with a hash size limitation of 20 bytes,
300-
* where <digest type> is "ima",
302+
* where <digest type> is either "ima" or "verity",
301303
* where <hash algo> is the hash_algo_name[] string.
302304
*/
303305
u8 buffer[DIGEST_TYPE_NAME_LEN_MAX + CRYPTO_MAX_ALG_NAME + 2 +
@@ -432,6 +434,8 @@ int ima_eventdigest_ngv2_init(struct ima_event_data *event_data,
432434
cur_digestsize = event_data->iint->ima_hash->length;
433435

434436
hash_algo = event_data->iint->ima_hash->algo;
437+
if (event_data->iint->flags & IMA_VERITY_REQUIRED)
438+
digest_type = DIGEST_TYPE_VERITY;
435439
out:
436440
return ima_eventdigest_init_common(cur_digest, cur_digestsize,
437441
digest_type, hash_algo,

security/integrity/integrity.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#define IMA_FAIL_UNVERIFIABLE_SIGS 0x10000000
4141
#define IMA_MODSIG_ALLOWED 0x20000000
4242
#define IMA_CHECK_BLACKLIST 0x40000000
43+
#define IMA_VERITY_REQUIRED 0x80000000
4344

4445
#define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \
4546
IMA_HASH | IMA_APPRAISE_SUBMASK)

0 commit comments

Comments
 (0)