765 lines · 19.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Copyright (C) 2013 Politecnico di Torino, Italy *                    TORSEC group -- https://security.polito.it * * Author: Roberto Sassu <roberto.sassu@polito.it> * * File: ima_template_lib.c *      Library of supported template fields. */ #include "ima_template_lib.h"#include <linux/xattr.h>#include <linux/evm.h> static bool ima_template_hash_algo_allowed(u8 algo){	if (algo == HASH_ALGO_SHA1 || algo == HASH_ALGO_MD5)		return true; 	return false;} enum data_formats {	DATA_FMT_DIGEST = 0,	DATA_FMT_DIGEST_WITH_ALGO,	DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO,	DATA_FMT_STRING,	DATA_FMT_HEX,	DATA_FMT_UINT}; enum digest_type {	DIGEST_TYPE_IMA,	DIGEST_TYPE_VERITY,	DIGEST_TYPE__LAST}; #define DIGEST_TYPE_NAME_LEN_MAX 7	/* including NUL */static const char * const digest_type_name[DIGEST_TYPE__LAST] = {	[DIGEST_TYPE_IMA] = "ima",	[DIGEST_TYPE_VERITY] = "verity"}; static int ima_write_template_field_data(const void *data, const u32 datalen,					 enum data_formats datafmt,					 struct ima_field_data *field_data){	u8 *buf, *buf_ptr;	u32 buflen = datalen; 	if (datafmt == DATA_FMT_STRING)		buflen = datalen + 1; 	buf = kzalloc(buflen, GFP_KERNEL);	if (!buf)		return -ENOMEM; 	memcpy(buf, data, datalen); 	/*	 * Replace all space characters with underscore for event names and	 * strings. This avoid that, during the parsing of a measurements list,	 * filenames with spaces or that end with the suffix ' (deleted)' are	 * split into multiple template fields (the space is the delimitator	 * character for measurements lists in ASCII format).	 */	if (datafmt == DATA_FMT_STRING) {		for (buf_ptr = buf; buf_ptr - buf < datalen; buf_ptr++)			if (*buf_ptr == ' ')				*buf_ptr = '_';	} 	field_data->data = buf;	field_data->len = buflen;	return 0;} static void ima_show_template_data_ascii(struct seq_file *m,					 enum ima_show_type show,					 enum data_formats datafmt,					 struct ima_field_data *field_data){	u8 *buf_ptr = field_data->data;	u32 buflen = field_data->len; 	switch (datafmt) {	case DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO:	case DATA_FMT_DIGEST_WITH_ALGO:		buf_ptr = strrchr(field_data->data, ':');		if (buf_ptr != field_data->data)			seq_printf(m, "%s", field_data->data); 		/* skip ':' and '\0' */		buf_ptr += 2;		buflen -= buf_ptr - field_data->data;		fallthrough;	case DATA_FMT_DIGEST:	case DATA_FMT_HEX:		if (!buflen)			break;		ima_print_digest(m, buf_ptr, buflen);		break;	case DATA_FMT_STRING:		seq_printf(m, "%s", buf_ptr);		break;	case DATA_FMT_UINT:		switch (field_data->len) {		case sizeof(u8):			seq_printf(m, "%u", *(u8 *)buf_ptr);			break;		case sizeof(u16):			if (ima_canonical_fmt)				seq_printf(m, "%u",					   le16_to_cpu(*(__le16 *)buf_ptr));			else				seq_printf(m, "%u", *(u16 *)buf_ptr);			break;		case sizeof(u32):			if (ima_canonical_fmt)				seq_printf(m, "%u",					   le32_to_cpu(*(__le32 *)buf_ptr));			else				seq_printf(m, "%u", *(u32 *)buf_ptr);			break;		case sizeof(u64):			if (ima_canonical_fmt)				seq_printf(m, "%llu",					   le64_to_cpu(*(__le64 *)buf_ptr));			else				seq_printf(m, "%llu", *(u64 *)buf_ptr);			break;		default:			break;		}		break;	default:		break;	}} static void ima_show_template_data_binary(struct seq_file *m,					  enum ima_show_type show,					  enum data_formats datafmt,					  struct ima_field_data *field_data){	u32 len = (show == IMA_SHOW_BINARY_OLD_STRING_FMT) ?	    strlen(field_data->data) : field_data->len; 	if (show != IMA_SHOW_BINARY_NO_FIELD_LEN) {		u32 field_len = !ima_canonical_fmt ?				len : (__force u32)cpu_to_le32(len); 		ima_putc(m, &field_len, sizeof(field_len));	} 	if (!len)		return; 	ima_putc(m, field_data->data, len);} static void ima_show_template_field_data(struct seq_file *m,					 enum ima_show_type show,					 enum data_formats datafmt,					 struct ima_field_data *field_data){	switch (show) {	case IMA_SHOW_ASCII:		ima_show_template_data_ascii(m, show, datafmt, field_data);		break;	case IMA_SHOW_BINARY:	case IMA_SHOW_BINARY_NO_FIELD_LEN:	case IMA_SHOW_BINARY_OLD_STRING_FMT:		ima_show_template_data_binary(m, show, datafmt, field_data);		break;	default:		break;	}} void ima_show_template_digest(struct seq_file *m, enum ima_show_type show,			      struct ima_field_data *field_data){	ima_show_template_field_data(m, show, DATA_FMT_DIGEST, field_data);} void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show,				 struct ima_field_data *field_data){	ima_show_template_field_data(m, show, DATA_FMT_DIGEST_WITH_ALGO,				     field_data);} void ima_show_template_digest_ngv2(struct seq_file *m, enum ima_show_type show,				   struct ima_field_data *field_data){	ima_show_template_field_data(m, show,				     DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO,				     field_data);} void ima_show_template_string(struct seq_file *m, enum ima_show_type show,			      struct ima_field_data *field_data){	ima_show_template_field_data(m, show, DATA_FMT_STRING, field_data);} void ima_show_template_sig(struct seq_file *m, enum ima_show_type show,			   struct ima_field_data *field_data){	ima_show_template_field_data(m, show, DATA_FMT_HEX, field_data);} void ima_show_template_buf(struct seq_file *m, enum ima_show_type show,			   struct ima_field_data *field_data){	ima_show_template_field_data(m, show, DATA_FMT_HEX, field_data);} void ima_show_template_uint(struct seq_file *m, enum ima_show_type show,			    struct ima_field_data *field_data){	ima_show_template_field_data(m, show, DATA_FMT_UINT, field_data);} /** * ima_parse_buf() - Parses lengths and data from an input buffer * @bufstartp:       Buffer start address. * @bufendp:         Buffer end address. * @bufcurp:         Pointer to remaining (non-parsed) data. * @maxfields:       Length of fields array. * @fields:          Array containing lengths and pointers of parsed data. * @curfields:       Number of array items containing parsed data. * @len_mask:        Bitmap (if bit is set, data length should not be parsed). * @enforce_mask:    Check if curfields == maxfields and/or bufcurp == bufendp. * @bufname:         String identifier of the input buffer. * * Return: 0 on success, -EINVAL on error. */int ima_parse_buf(void *bufstartp, void *bufendp, void **bufcurp,		  int maxfields, struct ima_field_data *fields, int *curfields,		  unsigned long *len_mask, int enforce_mask, char *bufname){	void *bufp = bufstartp;	int i; 	for (i = 0; i < maxfields; i++) {		if (len_mask == NULL || !test_bit(i, len_mask)) {			if (bufp > (bufendp - sizeof(u32)))				break; 			if (ima_canonical_fmt)				fields[i].len = le32_to_cpu(*(__le32 *)bufp);			else				fields[i].len = *(u32 *)bufp; 			bufp += sizeof(u32);		} 		if (bufp > (bufendp - fields[i].len))			break; 		fields[i].data = bufp;		bufp += fields[i].len;	} 	if ((enforce_mask & ENFORCE_FIELDS) && i != maxfields) {		pr_err("%s: nr of fields mismatch: expected: %d, current: %d\n",		       bufname, maxfields, i);		return -EINVAL;	} 	if ((enforce_mask & ENFORCE_BUFEND) && bufp != bufendp) {		pr_err("%s: buf end mismatch: expected: %p, current: %p\n",		       bufname, bufendp, bufp);		return -EINVAL;	} 	if (curfields)		*curfields = i; 	if (bufcurp)		*bufcurp = bufp; 	return 0;} static int ima_eventdigest_init_common(const u8 *digest, u32 digestsize,				       u8 digest_type, u8 hash_algo,				       struct ima_field_data *field_data){	/*	 * digest formats:	 *  - DATA_FMT_DIGEST: digest	 *  - DATA_FMT_DIGEST_WITH_ALGO: <hash algo> + ':' + '\0' + digest,	 *  - DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO:	 *	<digest type> + ':' + <hash algo> + ':' + '\0' + digest,	 *	 *    where 'DATA_FMT_DIGEST' is the original digest format ('d')	 *      with a hash size limitation of 20 bytes,	 *    where <digest type> is either "ima" or "verity",	 *    where <hash algo> is the hash_algo_name[] string.	 */	u8 buffer[DIGEST_TYPE_NAME_LEN_MAX + CRYPTO_MAX_ALG_NAME + 2 +		IMA_MAX_DIGEST_SIZE] = { 0 };	enum data_formats fmt = DATA_FMT_DIGEST;	u32 offset = 0; 	if (digest_type < DIGEST_TYPE__LAST && hash_algo < HASH_ALGO__LAST) {		fmt = DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO;		offset += 1 + sprintf(buffer, "%s:%s:",				      digest_type_name[digest_type],				      hash_algo_name[hash_algo]);	} else if (hash_algo < HASH_ALGO__LAST) {		fmt = DATA_FMT_DIGEST_WITH_ALGO;		offset += 1 + sprintf(buffer, "%s:",				      hash_algo_name[hash_algo]);	} 	if (digest) {		memcpy(buffer + offset, digest, digestsize);	} else {		/*		 * If digest is NULL, the event being recorded is a violation.		 * Make room for the digest by increasing the offset by the		 * hash algorithm digest size. If the hash algorithm is not		 * specified increase the offset by IMA_DIGEST_SIZE which		 * fits SHA1 or MD5		 */		if (hash_algo < HASH_ALGO__LAST)			offset += hash_digest_size[hash_algo];		else			offset += IMA_DIGEST_SIZE;	} 	return ima_write_template_field_data(buffer, offset + digestsize,					     fmt, field_data);} /* * This function writes the digest of an event (with size limit). */int ima_eventdigest_init(struct ima_event_data *event_data,			 struct ima_field_data *field_data){	struct ima_max_digest_data hash;	struct ima_digest_data *hash_hdr = container_of(&hash.hdr,						struct ima_digest_data, hdr);	u8 *cur_digest = NULL;	u32 cur_digestsize = 0;	struct inode *inode;	int result; 	memset(&hash, 0, sizeof(hash)); 	if (event_data->violation)	/* recording a violation. */		goto out; 	if (ima_template_hash_algo_allowed(event_data->iint->ima_hash->algo)) {		cur_digest = event_data->iint->ima_hash->digest;		cur_digestsize = event_data->iint->ima_hash->length;		goto out;	} 	if ((const char *)event_data->filename == boot_aggregate_name ||	    (const char *)event_data->filename == boot_aggregate_late_name) {		if (ima_tpm_chip) {			hash.hdr.algo = HASH_ALGO_SHA1;			result = ima_calc_boot_aggregate(hash_hdr); 			/* algo can change depending on available PCR banks */			if (!result && hash.hdr.algo != HASH_ALGO_SHA1)				result = -EINVAL; 			if (result < 0)				memset(&hash, 0, sizeof(hash));		} 		cur_digest = hash_hdr->digest;		cur_digestsize = hash_digest_size[HASH_ALGO_SHA1];		goto out;	} 	if (!event_data->file)	/* missing info to re-calculate the digest */		return -EINVAL; 	inode = file_inode(event_data->file);	hash.hdr.algo = ima_template_hash_algo_allowed(ima_hash_algo) ?	    ima_hash_algo : HASH_ALGO_SHA1;	result = ima_calc_file_hash(event_data->file, hash_hdr);	if (result) {		integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode,				    event_data->filename, "collect_data",				    "failed", result, 0);		return result;	}	cur_digest = hash_hdr->digest;	cur_digestsize = hash.hdr.length;out:	return ima_eventdigest_init_common(cur_digest, cur_digestsize,					   DIGEST_TYPE__LAST, HASH_ALGO__LAST,					   field_data);} /* * This function writes the digest of an event (without size limit). */int ima_eventdigest_ng_init(struct ima_event_data *event_data,			    struct ima_field_data *field_data){	u8 *cur_digest = NULL, hash_algo = ima_hash_algo;	u32 cur_digestsize = 0; 	if (event_data->violation)	/* recording a violation. */		goto out; 	cur_digest = event_data->iint->ima_hash->digest;	cur_digestsize = event_data->iint->ima_hash->length; 	hash_algo = event_data->iint->ima_hash->algo;out:	return ima_eventdigest_init_common(cur_digest, cur_digestsize,					   DIGEST_TYPE__LAST, hash_algo,					   field_data);} /* * This function writes the digest of an event (without size limit), * prefixed with both the digest type and hash algorithm. */int ima_eventdigest_ngv2_init(struct ima_event_data *event_data,			      struct ima_field_data *field_data){	u8 *cur_digest = NULL, hash_algo = ima_hash_algo;	u32 cur_digestsize = 0;	u8 digest_type = DIGEST_TYPE_IMA; 	if (event_data->violation)	/* recording a violation. */		goto out; 	cur_digest = event_data->iint->ima_hash->digest;	cur_digestsize = event_data->iint->ima_hash->length; 	hash_algo = event_data->iint->ima_hash->algo;	if (event_data->iint->flags & IMA_VERITY_REQUIRED)		digest_type = DIGEST_TYPE_VERITY;out:	return ima_eventdigest_init_common(cur_digest, cur_digestsize,					   digest_type, hash_algo,					   field_data);} /* * This function writes the digest of the file which is expected to match the * digest contained in the file's appended signature. */int ima_eventdigest_modsig_init(struct ima_event_data *event_data,				struct ima_field_data *field_data){	enum hash_algo hash_algo;	const u8 *cur_digest;	u32 cur_digestsize; 	if (!event_data->modsig)		return 0; 	if (event_data->violation) {		/* Recording a violation. */		hash_algo = HASH_ALGO_SHA1;		cur_digest = NULL;		cur_digestsize = 0;	} else {		int rc; 		rc = ima_get_modsig_digest(event_data->modsig, &hash_algo,					   &cur_digest, &cur_digestsize);		if (rc)			return rc;		else if (hash_algo == HASH_ALGO__LAST || cur_digestsize == 0)			/* There was some error collecting the digest. */			return -EINVAL;	} 	return ima_eventdigest_init_common(cur_digest, cur_digestsize,					   DIGEST_TYPE__LAST, hash_algo,					   field_data);} static int ima_eventname_init_common(struct ima_event_data *event_data,				     struct ima_field_data *field_data,				     bool size_limit){	const char *cur_filename = NULL;	struct name_snapshot filename;	u32 cur_filename_len = 0;	bool snapshot = false;	int ret; 	BUG_ON(event_data->filename == NULL && event_data->file == NULL);

Lines 1–500 of 765.