2019-06-01 11:08:55 +03:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2011-03-09 22:13:22 +03:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2008 IBM Corporation
|
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* Mimi Zohar <zohar@us.ibm.com>
|
|
|
|
*
|
|
|
|
* File: integrity_iint.c
|
|
|
|
* - implements the integrity hooks: integrity_inode_alloc,
|
|
|
|
* integrity_inode_free
|
|
|
|
* - cache integrity information associated with an inode
|
|
|
|
* using a rbtree tree.
|
|
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
2018-12-09 23:36:29 +03:00
|
|
|
#include <linux/init.h>
|
2011-03-09 22:13:22 +03:00
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/rbtree.h>
|
2014-11-05 18:01:12 +03:00
|
|
|
#include <linux/file.h>
|
|
|
|
#include <linux/uaccess.h>
|
2018-05-12 02:12:34 +03:00
|
|
|
#include <linux/security.h>
|
2018-10-11 03:18:21 +03:00
|
|
|
#include <linux/lsm_hooks.h>
|
2011-03-09 22:13:22 +03:00
|
|
|
#include "integrity.h"
|
|
|
|
|
|
|
|
static struct rb_root integrity_iint_tree = RB_ROOT;
|
2012-02-08 23:15:42 +04:00
|
|
|
static DEFINE_RWLOCK(integrity_iint_lock);
|
2011-03-09 22:13:22 +03:00
|
|
|
static struct kmem_cache *iint_cache __read_mostly;
|
|
|
|
|
2018-05-12 02:12:34 +03:00
|
|
|
struct dentry *integrity_dir;
|
|
|
|
|
2011-03-09 22:13:22 +03:00
|
|
|
/*
|
|
|
|
* __integrity_iint_find - return the iint associated with an inode
|
|
|
|
*/
|
|
|
|
static struct integrity_iint_cache *__integrity_iint_find(struct inode *inode)
|
|
|
|
{
|
|
|
|
struct integrity_iint_cache *iint;
|
|
|
|
struct rb_node *n = integrity_iint_tree.rb_node;
|
|
|
|
|
|
|
|
while (n) {
|
|
|
|
iint = rb_entry(n, struct integrity_iint_cache, rb_node);
|
|
|
|
|
|
|
|
if (inode < iint->inode)
|
|
|
|
n = n->rb_left;
|
|
|
|
else if (inode > iint->inode)
|
|
|
|
n = n->rb_right;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!n)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return iint;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* integrity_iint_find - return the iint associated with an inode
|
|
|
|
*/
|
|
|
|
struct integrity_iint_cache *integrity_iint_find(struct inode *inode)
|
|
|
|
{
|
|
|
|
struct integrity_iint_cache *iint;
|
|
|
|
|
|
|
|
if (!IS_IMA(inode))
|
|
|
|
return NULL;
|
|
|
|
|
2012-02-08 23:15:42 +04:00
|
|
|
read_lock(&integrity_iint_lock);
|
2011-03-09 22:13:22 +03:00
|
|
|
iint = __integrity_iint_find(inode);
|
2012-02-08 23:15:42 +04:00
|
|
|
read_unlock(&integrity_iint_lock);
|
2011-03-09 22:13:22 +03:00
|
|
|
|
|
|
|
return iint;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void iint_free(struct integrity_iint_cache *iint)
|
|
|
|
{
|
2013-04-25 11:44:04 +04:00
|
|
|
kfree(iint->ima_hash);
|
|
|
|
iint->ima_hash = NULL;
|
2011-03-09 22:13:22 +03:00
|
|
|
iint->version = 0;
|
|
|
|
iint->flags = 0UL;
|
2018-01-23 18:00:41 +03:00
|
|
|
iint->atomic_flags = 0UL;
|
2012-12-04 02:08:11 +04:00
|
|
|
iint->ima_file_status = INTEGRITY_UNKNOWN;
|
|
|
|
iint->ima_mmap_status = INTEGRITY_UNKNOWN;
|
|
|
|
iint->ima_bprm_status = INTEGRITY_UNKNOWN;
|
2015-11-19 20:39:22 +03:00
|
|
|
iint->ima_read_status = INTEGRITY_UNKNOWN;
|
2018-01-09 00:36:20 +03:00
|
|
|
iint->ima_creds_status = INTEGRITY_UNKNOWN;
|
2011-08-15 16:30:11 +04:00
|
|
|
iint->evm_status = INTEGRITY_UNKNOWN;
|
2016-06-01 21:14:00 +03:00
|
|
|
iint->measured_pcrs = 0;
|
2011-03-09 22:13:22 +03:00
|
|
|
kmem_cache_free(iint_cache, iint);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-10-19 13:04:40 +04:00
|
|
|
* integrity_inode_get - find or allocate an iint associated with an inode
|
2011-03-09 22:13:22 +03:00
|
|
|
* @inode: pointer to the inode
|
2011-10-19 13:04:40 +04:00
|
|
|
* @return: allocated iint
|
|
|
|
*
|
|
|
|
* Caller must lock i_mutex
|
2011-03-09 22:13:22 +03:00
|
|
|
*/
|
2011-10-19 13:04:40 +04:00
|
|
|
struct integrity_iint_cache *integrity_inode_get(struct inode *inode)
|
2011-03-09 22:13:22 +03:00
|
|
|
{
|
|
|
|
struct rb_node **p;
|
2011-10-19 13:04:40 +04:00
|
|
|
struct rb_node *node, *parent = NULL;
|
|
|
|
struct integrity_iint_cache *iint, *test_iint;
|
2011-03-09 22:13:22 +03:00
|
|
|
|
integrity: double check iint_cache was initialized
The kernel may be built with multiple LSMs, but only a subset may be
enabled on the boot command line by specifying "lsm=". Not including
"integrity" on the ordered LSM list may result in a NULL deref.
As reported by Dmitry Vyukov:
in qemu:
qemu-system-x86_64 -enable-kvm -machine q35,nvdimm -cpu
max,migratable=off -smp 4 -m 4G,slots=4,maxmem=16G -hda
wheezy.img -kernel arch/x86/boot/bzImage -nographic -vga std
-soundhw all -usb -usbdevice tablet -bt hci -bt device:keyboard
-net user,host=10.0.2.10,hostfwd=tcp::10022-:22 -net
nic,model=virtio-net-pci -object
memory-backend-file,id=pmem1,share=off,mem-path=/dev/zero,size=64M
-device nvdimm,id=nvdimm1,memdev=pmem1 -append "console=ttyS0
root=/dev/sda earlyprintk=serial rodata=n oops=panic panic_on_warn=1
panic=86400 lsm=smack numa=fake=2 nopcid dummy_hcd.num=8" -pidfile
vm_pid -m 2G -cpu host
But it crashes on NULL deref in integrity_inode_get during boot:
Run /sbin/init as init process
BUG: kernel NULL pointer dereference, address: 000000000000001c
PGD 0 P4D 0
Oops: 0000 [#1] PREEMPT SMP KASAN
CPU: 3 PID: 1 Comm: swapper/0 Not tainted 5.12.0-rc2+ #97
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS
rel-1.13.0-44-g88ab0c15525c-prebuilt.qemu.org 04/01/2014
RIP: 0010:kmem_cache_alloc+0x2b/0x370 mm/slub.c:2920
Code: 57 41 56 41 55 41 54 41 89 f4 55 48 89 fd 53 48 83 ec 10 44 8b
3d d9 1f 90 0b 65 48 8b 04 25 28 00 00 00 48 89 44 24 08 31 c0 <8b> 5f
1c 4cf
RSP: 0000:ffffc9000032f9d8 EFLAGS: 00010246
RAX: 0000000000000000 RBX: ffff888017fc4f00 RCX: 0000000000000000
RDX: ffff888040220000 RSI: 0000000000000c40 RDI: 0000000000000000
RBP: 0000000000000000 R08: 0000000000000000 R09: ffff888019263627
R10: ffffffff83937cd1 R11: 0000000000000000 R12: 0000000000000c40
R13: ffff888019263538 R14: 0000000000000000 R15: 0000000000ffffff
FS: 0000000000000000(0000) GS:ffff88802d180000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000000000000001c CR3: 000000000b48e000 CR4: 0000000000750ee0
DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
PKRU: 55555554
Call Trace:
integrity_inode_get+0x47/0x260 security/integrity/iint.c:105
process_measurement+0x33d/0x17e0 security/integrity/ima/ima_main.c:237
ima_bprm_check+0xde/0x210 security/integrity/ima/ima_main.c:474
security_bprm_check+0x7d/0xa0 security/security.c:845
search_binary_handler fs/exec.c:1708 [inline]
exec_binprm fs/exec.c:1761 [inline]
bprm_execve fs/exec.c:1830 [inline]
bprm_execve+0x764/0x19a0 fs/exec.c:1792
kernel_execve+0x370/0x460 fs/exec.c:1973
try_to_run_init_process+0x14/0x4e init/main.c:1366
kernel_init+0x11d/0x1b8 init/main.c:1477
ret_from_fork+0x1f/0x30 arch/x86/entry/entry_64.S:294
Modules linked in:
CR2: 000000000000001c
---[ end trace 22d601a500de7d79 ]---
Since LSMs and IMA may be configured at build time, but not enabled at
run time, panic the system if "integrity" was not initialized before use.
Reported-by: Dmitry Vyukov <dvyukov@google.com>
Fixes: 79f7865d844c ("LSM: Introduce "lsm=" for boottime LSM selection")
Cc: stable@vger.kernel.org
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2021-03-19 18:17:23 +03:00
|
|
|
/*
|
|
|
|
* The integrity's "iint_cache" is initialized at security_init(),
|
|
|
|
* unless it is not included in the ordered list of LSMs enabled
|
|
|
|
* on the boot command line.
|
|
|
|
*/
|
|
|
|
if (!iint_cache)
|
|
|
|
panic("%s: lsm=integrity required.\n", __func__);
|
|
|
|
|
2011-10-19 13:04:40 +04:00
|
|
|
iint = integrity_iint_find(inode);
|
|
|
|
if (iint)
|
|
|
|
return iint;
|
2011-03-09 22:13:22 +03:00
|
|
|
|
2011-10-19 13:04:40 +04:00
|
|
|
iint = kmem_cache_alloc(iint_cache, GFP_NOFS);
|
|
|
|
if (!iint)
|
|
|
|
return NULL;
|
2011-03-09 22:13:22 +03:00
|
|
|
|
2012-02-08 23:15:42 +04:00
|
|
|
write_lock(&integrity_iint_lock);
|
2011-03-09 22:13:22 +03:00
|
|
|
|
|
|
|
p = &integrity_iint_tree.rb_node;
|
|
|
|
while (*p) {
|
|
|
|
parent = *p;
|
|
|
|
test_iint = rb_entry(parent, struct integrity_iint_cache,
|
|
|
|
rb_node);
|
|
|
|
if (inode < test_iint->inode)
|
|
|
|
p = &(*p)->rb_left;
|
|
|
|
else
|
2011-10-19 13:04:40 +04:00
|
|
|
p = &(*p)->rb_right;
|
2011-03-09 22:13:22 +03:00
|
|
|
}
|
|
|
|
|
2011-10-19 13:04:40 +04:00
|
|
|
iint->inode = inode;
|
|
|
|
node = &iint->rb_node;
|
2011-03-09 22:13:22 +03:00
|
|
|
inode->i_flags |= S_IMA;
|
2011-10-19 13:04:40 +04:00
|
|
|
rb_link_node(node, parent, p);
|
|
|
|
rb_insert_color(node, &integrity_iint_tree);
|
2011-03-09 22:13:22 +03:00
|
|
|
|
2012-02-08 23:15:42 +04:00
|
|
|
write_unlock(&integrity_iint_lock);
|
2011-10-19 13:04:40 +04:00
|
|
|
return iint;
|
2011-03-09 22:13:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* integrity_inode_free - called on security_inode_free
|
|
|
|
* @inode: pointer to the inode
|
|
|
|
*
|
|
|
|
* Free the integrity information(iint) associated with an inode.
|
|
|
|
*/
|
|
|
|
void integrity_inode_free(struct inode *inode)
|
|
|
|
{
|
|
|
|
struct integrity_iint_cache *iint;
|
|
|
|
|
|
|
|
if (!IS_IMA(inode))
|
|
|
|
return;
|
|
|
|
|
2012-02-08 23:15:42 +04:00
|
|
|
write_lock(&integrity_iint_lock);
|
2011-03-09 22:13:22 +03:00
|
|
|
iint = __integrity_iint_find(inode);
|
|
|
|
rb_erase(&iint->rb_node, &integrity_iint_tree);
|
2012-02-08 23:15:42 +04:00
|
|
|
write_unlock(&integrity_iint_lock);
|
2011-03-09 22:13:22 +03:00
|
|
|
|
|
|
|
iint_free(iint);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void init_once(void *foo)
|
|
|
|
{
|
|
|
|
struct integrity_iint_cache *iint = foo;
|
|
|
|
|
2014-03-04 20:04:20 +04:00
|
|
|
memset(iint, 0, sizeof(*iint));
|
2012-12-04 02:08:11 +04:00
|
|
|
iint->ima_file_status = INTEGRITY_UNKNOWN;
|
|
|
|
iint->ima_mmap_status = INTEGRITY_UNKNOWN;
|
|
|
|
iint->ima_bprm_status = INTEGRITY_UNKNOWN;
|
2015-11-19 20:39:22 +03:00
|
|
|
iint->ima_read_status = INTEGRITY_UNKNOWN;
|
2018-01-09 00:36:20 +03:00
|
|
|
iint->ima_creds_status = INTEGRITY_UNKNOWN;
|
2011-05-06 12:34:17 +04:00
|
|
|
iint->evm_status = INTEGRITY_UNKNOWN;
|
ima: re-introduce own integrity cache lock
Before IMA appraisal was introduced, IMA was using own integrity cache
lock along with i_mutex. process_measurement and ima_file_free took
the iint->mutex first and then the i_mutex, while setxattr, chmod and
chown took the locks in reverse order. To resolve the potential deadlock,
i_mutex was moved to protect entire IMA functionality and the redundant
iint->mutex was eliminated.
Solution was based on the assumption that filesystem code does not take
i_mutex further. But when file is opened with O_DIRECT flag, direct-io
implementation takes i_mutex and produces deadlock. Furthermore, certain
other filesystem operations, such as llseek, also take i_mutex.
More recently some filesystems have replaced their filesystem specific
lock with the global i_rwsem to read a file. As a result, when IMA
attempts to calculate the file hash, reading the file attempts to take
the i_rwsem again.
To resolve O_DIRECT related deadlock problem, this patch re-introduces
iint->mutex. But to eliminate the original chmod() related deadlock
problem, this patch eliminates the requirement for chmod hooks to take
the iint->mutex by introducing additional atomic iint->attr_flags to
indicate calling of the hooks. The allowed locking order is to take
the iint->mutex first and then the i_rwsem.
Original flags were cleared in chmod(), setxattr() or removwxattr()
hooks and tested when file was closed or opened again. New atomic flags
are set or cleared in those hooks and tested to clear iint->flags on
close or on open.
Atomic flags are following:
* IMA_CHANGE_ATTR - indicates that chATTR() was called (chmod, chown,
chgrp) and file attributes have changed. On file open, it causes IMA
to clear iint->flags to re-evaluate policy and perform IMA functions
again.
* IMA_CHANGE_XATTR - indicates that setxattr or removexattr was called
and extended attributes have changed. On file open, it causes IMA to
clear iint->flags IMA_DONE_MASK to re-appraise.
* IMA_UPDATE_XATTR - indicates that security.ima needs to be updated.
It is cleared if file policy changes and no update is needed.
* IMA_DIGSIG - indicates that file security.ima has signature and file
security.ima must not update to file has on file close.
* IMA_MUST_MEASURE - indicates the file is in the measurement policy.
Fixes: Commit 6552321831dc ("xfs: remove i_iolock and use i_rwsem in
the VFS inode instead")
Signed-off-by: Dmitry Kasatkin <dmitry.kasatkin@huawei.com>
Signed-off-by: Mimi Zohar <zohar@linux.vnet.ibm.com>
2017-12-05 22:06:34 +03:00
|
|
|
mutex_init(&iint->mutex);
|
2011-03-09 22:13:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static int __init integrity_iintcache_init(void)
|
|
|
|
{
|
|
|
|
iint_cache =
|
|
|
|
kmem_cache_create("iint_cache", sizeof(struct integrity_iint_cache),
|
|
|
|
0, SLAB_PANIC, init_once);
|
|
|
|
return 0;
|
|
|
|
}
|
2018-10-11 03:18:23 +03:00
|
|
|
DEFINE_LSM(integrity) = {
|
2018-10-11 03:18:24 +03:00
|
|
|
.name = "integrity",
|
2018-10-11 03:18:23 +03:00
|
|
|
.init = integrity_iintcache_init,
|
|
|
|
};
|
2014-11-05 18:01:12 +03:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* integrity_kernel_read - read data from the file
|
|
|
|
*
|
|
|
|
* This is a function for reading file content instead of kernel_read().
|
|
|
|
* It does not perform locking checks to ensure it cannot be blocked.
|
|
|
|
* It does not perform security checks because it is irrelevant for IMA.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
int integrity_kernel_read(struct file *file, loff_t offset,
|
2017-06-08 04:49:10 +03:00
|
|
|
void *addr, unsigned long count)
|
2014-11-05 18:01:12 +03:00
|
|
|
{
|
2020-05-08 09:54:27 +03:00
|
|
|
return __kernel_read(file, addr, count, &offset);
|
2014-11-05 18:01:12 +03:00
|
|
|
}
|
|
|
|
|
2014-11-05 18:01:15 +03:00
|
|
|
/*
|
|
|
|
* integrity_load_keys - load integrity keys hook
|
|
|
|
*
|
|
|
|
* Hooks is called from init/main.c:kernel_init_freeable()
|
|
|
|
* when rootfs is ready
|
|
|
|
*/
|
|
|
|
void __init integrity_load_keys(void)
|
|
|
|
{
|
|
|
|
ima_load_x509();
|
2015-10-22 21:26:21 +03:00
|
|
|
evm_load_x509();
|
2014-11-05 18:01:15 +03:00
|
|
|
}
|
2018-05-12 02:12:34 +03:00
|
|
|
|
|
|
|
static int __init integrity_fs_init(void)
|
|
|
|
{
|
|
|
|
integrity_dir = securityfs_create_dir("integrity", NULL);
|
|
|
|
if (IS_ERR(integrity_dir)) {
|
2018-06-05 13:25:45 +03:00
|
|
|
int ret = PTR_ERR(integrity_dir);
|
|
|
|
|
|
|
|
if (ret != -ENODEV)
|
|
|
|
pr_err("Unable to create integrity sysfs dir: %d\n",
|
|
|
|
ret);
|
2018-05-12 02:12:34 +03:00
|
|
|
integrity_dir = NULL;
|
2018-06-05 13:25:45 +03:00
|
|
|
return ret;
|
2018-05-12 02:12:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
late_initcall(integrity_fs_init)
|