blob: ce073edbc12996aef0f0080846239c73576985a2 [file] [log] [blame]
/*
* Copyright (c) 2014-2015, 2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "seemp: %s: " fmt, __func__
#include "seemp_logk.h"
#include "seemp_ringbuf.h"
#ifndef VM_RESERVED
#define VM_RESERVED (VM_DONTEXPAND | VM_DONTDUMP)
#endif
#define MASK_BUFFER_SIZE 256
#define FOUR_MB 4
#define YEAR_BASE 1900
static struct seemp_logk_dev *slogk_dev;
static unsigned int ring_sz = FOUR_MB;
/*
* default is besteffort, apps do not get blocked
*/
static unsigned int block_apps;
/*
* When this flag is turned on,
* kmalloc should be used for ring buf allocation
* otherwise it is vmalloc.
* default is to use vmalloc
* kmalloc has a limit of 4MB
*/
unsigned int kmalloc_flag;
static struct class *cl;
static rwlock_t filter_lock;
static struct seemp_source_mask *pmask;
static unsigned int num_sources;
static long seemp_logk_reserve_rdblks(
struct seemp_logk_dev *sdev, unsigned long arg);
static long seemp_logk_set_mask(unsigned long arg);
static long seemp_logk_set_mapping(unsigned long arg);
static long seemp_logk_check_filter(unsigned long arg);
void* (*seemp_logk_kernel_begin)(char **buf);
void (*seemp_logk_kernel_end)(void *blck);
/*
* the last param is the permission bits *
* kernel logging is done in three steps:
* (1) fetch a block, fill everything except payload.
* (2) return payload pointer to the caller.
* (3) caller fills its data directly into the payload area.
* (4) caller invoked finish_record(), to finish writing.
*/
void *seemp_logk_kernel_start_record(char **buf)
{
struct seemp_logk_blk *blk;
struct timespec now;
struct tm ts;
int idx;
int ret;
DEFINE_WAIT(write_wait);
ret = 0;
idx = 0;
now = current_kernel_time();
blk = ringbuf_fetch_wr_block(slogk_dev);
if (!blk) {
/*
* there is no blk to write
* if block_apps == 0; quietly return
*/
if (!block_apps) {
*buf = NULL;
return NULL;
}
/*else wait for the blks to be available*/
while (1) {
mutex_lock(&slogk_dev->lock);
prepare_to_wait(&slogk_dev->writers_wq,
&write_wait, TASK_INTERRUPTIBLE);
ret = (slogk_dev->num_write_avail_blks <= 0);
if (!ret) {
/* don't have to wait*/
break;
}
mutex_unlock(&slogk_dev->lock);
if (signal_pending(current)) {
ret = -EINTR;
break;
}
schedule();
}
finish_wait(&slogk_dev->writers_wq, &write_wait);
if (ret)
return NULL;
idx = slogk_dev->write_idx;
slogk_dev->write_idx =
(slogk_dev->write_idx + 1) % slogk_dev->num_tot_blks;
slogk_dev->num_write_avail_blks--;
slogk_dev->num_write_in_prog_blks++;
slogk_dev->num_writers++;
blk = &slogk_dev->ring[idx];
/*mark block invalid*/
blk->status = 0x0;
mutex_unlock(&slogk_dev->lock);
}
blk->version = OBSERVER_VERSION;
blk->pid = current->tgid;
blk->tid = current->pid;
blk->uid = (current_uid()).val;
blk->sec = now.tv_sec;
blk->nsec = now.tv_nsec;
strlcpy(blk->appname, current->comm, TASK_COMM_LEN);
time_to_tm(now.tv_sec, 0, &ts);
ts.tm_year += YEAR_BASE;
ts.tm_mon += 1;
snprintf(blk->ts, TS_SIZE, "%04ld-%02d-%02d %02d:%02d:%02d",
ts.tm_year, ts.tm_mon, ts.tm_mday,
ts.tm_hour, ts.tm_min, ts.tm_sec);
*buf = blk->payload.msg;
return blk;
}
void seemp_logk_kernel_end_record(void *blck)
{
struct seemp_logk_blk *blk = (struct seemp_logk_blk *)blck;
if (blk) {
/*update status at the very end*/
blk->status |= 0x1;
blk->uid = (current_uid()).val;
ringbuf_finish_writer(slogk_dev, blk);
}
}
static int seemp_logk_usr_record(const char __user *buf, size_t count)
{
struct seemp_logk_blk *blk;
struct seemp_logk_blk usr_blk;
struct seemp_logk_blk *local_blk;
struct timespec now;
struct tm ts;
int idx, ret;
DEFINE_WAIT(write_wait);
if (buf) {
local_blk = (struct seemp_logk_blk *)buf;
if (copy_from_user(&usr_blk.pid, &local_blk->pid,
sizeof(usr_blk.pid)) != 0)
return -EFAULT;
if (copy_from_user(&usr_blk.tid, &local_blk->tid,
sizeof(usr_blk.tid)) != 0)
return -EFAULT;
if (copy_from_user(&usr_blk.uid, &local_blk->uid,
sizeof(usr_blk.uid)) != 0)
return -EFAULT;
if (copy_from_user(&usr_blk.len, &local_blk->len,
sizeof(usr_blk.len)) != 0)
return -EFAULT;
if (copy_from_user(&usr_blk.payload, &local_blk->payload,
sizeof(struct blk_payload)) != 0)
return -EFAULT;
} else {
return -EFAULT;
}
idx = ret = 0;
now = current_kernel_time();
blk = ringbuf_fetch_wr_block(slogk_dev);
if (!blk) {
if (!block_apps)
return 0;
while (1) {
mutex_lock(&slogk_dev->lock);
prepare_to_wait(&slogk_dev->writers_wq,
&write_wait,
TASK_INTERRUPTIBLE);
ret = (slogk_dev->num_write_avail_blks <= 0);
if (!ret)
break;
mutex_unlock(&slogk_dev->lock);
if (signal_pending(current)) {
ret = -EINTR;
break;
}
schedule();
}
finish_wait(&slogk_dev->writers_wq, &write_wait);
if (ret)
return -EINTR;
idx = slogk_dev->write_idx;
slogk_dev->write_idx =
(slogk_dev->write_idx + 1) % slogk_dev->num_tot_blks;
slogk_dev->num_write_avail_blks--;
slogk_dev->num_write_in_prog_blks++;
slogk_dev->num_writers++;
blk = &slogk_dev->ring[idx];
/*mark block invalid*/
blk->status = 0x0;
mutex_unlock(&slogk_dev->lock);
}
if (usr_blk.len > sizeof(struct blk_payload)-1)
usr_blk.len = sizeof(struct blk_payload)-1;
memcpy(&blk->payload, &usr_blk.payload, sizeof(struct blk_payload));
blk->pid = usr_blk.pid;
blk->uid = usr_blk.uid;
blk->tid = usr_blk.tid;
blk->sec = now.tv_sec;
blk->nsec = now.tv_nsec;
time_to_tm(now.tv_sec, 0, &ts);
ts.tm_year += YEAR_BASE;
ts.tm_mon += 1;
snprintf(blk->ts, TS_SIZE, "%02ld-%02d-%02d %02d:%02d:%02d",
ts.tm_year, ts.tm_mon, ts.tm_mday,
ts.tm_hour, ts.tm_min, ts.tm_sec);
strlcpy(blk->appname, current->comm, TASK_COMM_LEN);
blk->status |= 0x1;
ringbuf_finish_writer(slogk_dev, blk);
return ret;
}
static void seemp_logk_attach(void)
{
seemp_logk_kernel_end = seemp_logk_kernel_end_record;
seemp_logk_kernel_begin = seemp_logk_kernel_start_record;
}
static void seemp_logk_detach(void)
{
seemp_logk_kernel_begin = NULL;
seemp_logk_kernel_end = NULL;
}
static ssize_t
seemp_logk_write(struct file *file, const char __user *buf, size_t count,
loff_t *ppos)
{
return seemp_logk_usr_record(buf, count);
}
static int
seemp_logk_open(struct inode *inode, struct file *filp)
{
int ret;
/*disallow seeks on this file*/
ret = nonseekable_open(inode, filp);
if (ret) {
pr_err("ret= %d\n", ret);
return ret;
}
slogk_dev->minor = iminor(inode);
filp->private_data = slogk_dev;
return 0;
}
static bool seemp_logk_get_bit_from_vector(__u8 *pVec, __u32 index)
{
unsigned int byte_num = index/8;
unsigned int bit_num = index%8;
unsigned char byte;
if (DIV_ROUND_UP(index, 8) > MASK_BUFFER_SIZE)
return false;
byte = pVec[byte_num];
return !(byte & (1 << bit_num));
}
static long seemp_logk_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct seemp_logk_dev *sdev;
int ret;
sdev = (struct seemp_logk_dev *) filp->private_data;
if (cmd == SEEMP_CMD_RESERVE_RDBLKS) {
return seemp_logk_reserve_rdblks(sdev, arg);
} else if (cmd == SEEMP_CMD_RELEASE_RDBLKS) {
mutex_lock(&sdev->lock);
sdev->read_idx = (sdev->read_idx + sdev->num_read_in_prog_blks)
% sdev->num_tot_blks;
sdev->num_write_avail_blks += sdev->num_read_in_prog_blks;
ret = sdev->num_read_in_prog_blks;
sdev->num_read_in_prog_blks = 0;
/*wake up any waiting writers*/
mutex_unlock(&sdev->lock);
if (ret && block_apps)
wake_up_interruptible(&sdev->writers_wq);
} else if (cmd == SEEMP_CMD_GET_RINGSZ) {
if (copy_to_user((unsigned int *)arg, &sdev->ring_sz,
sizeof(unsigned int)))
return -EFAULT;
} else if (cmd == SEEMP_CMD_GET_BLKSZ) {
if (copy_to_user((unsigned int *)arg, &sdev->blk_sz,
sizeof(unsigned int)))
return -EFAULT;
} else if (cmd == SEEMP_CMD_SET_MASK) {
return seemp_logk_set_mask(arg);
} else if (cmd == SEEMP_CMD_SET_MAPPING) {
return seemp_logk_set_mapping(arg);
} else if (cmd == SEEMP_CMD_CHECK_FILTER) {
return seemp_logk_check_filter(arg);
}
pr_err("Invalid Request %X\n", cmd);
return -ENOIOCTLCMD;
}
static long seemp_logk_reserve_rdblks(
struct seemp_logk_dev *sdev, unsigned long arg)
{
int ret;
struct read_range rrange;
DEFINE_WAIT(read_wait);
mutex_lock(&sdev->lock);
if (sdev->num_writers > 0 || sdev->num_read_avail_blks <= 0) {
ret = -EPERM;
pr_debug("(reserve): blocking, cannot read.\n");
pr_debug("num_writers=%d num_read_avail_blks=%d\n",
sdev->num_writers,
sdev->num_read_avail_blks);
mutex_unlock(&sdev->lock);
/*
* unlock the device
* wait on a wait queue
* after wait, grab the dev lock again
*/
while (1) {
mutex_lock(&sdev->lock);
prepare_to_wait(&sdev->readers_wq, &read_wait,
TASK_INTERRUPTIBLE);
ret = (sdev->num_writers > 0 ||
sdev->num_read_avail_blks <= 0);
if (!ret) {
/*don't have to wait*/
break;
}
mutex_unlock(&sdev->lock);
if (signal_pending(current)) {
ret = -EINTR;
break;
}
schedule();
}
finish_wait(&sdev->readers_wq, &read_wait);
if (ret)
return -EINTR;
}
/*sdev->lock is held at this point*/
sdev->num_read_in_prog_blks = sdev->num_read_avail_blks;
sdev->num_read_avail_blks = 0;
rrange.start_idx = sdev->read_idx;
rrange.num = sdev->num_read_in_prog_blks;
mutex_unlock(&sdev->lock);
if (copy_to_user((unsigned int *)arg, &rrange,
sizeof(struct read_range)))
return -EFAULT;
return 0;
}
static long seemp_logk_set_mask(unsigned long arg)
{
__u8 buffer[256];
int i;
unsigned int num_elements;
if (copy_from_user(&num_elements,
(unsigned int __user *) arg, sizeof(unsigned int)))
return -EFAULT;
read_lock(&filter_lock);
if (num_sources == 0) {
read_unlock(&filter_lock);
return -EINVAL;
}
if (num_elements == 0 ||
DIV_ROUND_UP(num_sources, 8) > MASK_BUFFER_SIZE) {
read_unlock(&filter_lock);
return -EINVAL;
}
if (copy_from_user(buffer,
(__u8 *)arg, DIV_ROUND_UP(num_sources, 8))) {
read_unlock(&filter_lock);
return -EFAULT;
}
read_unlock(&filter_lock);
write_lock(&filter_lock);
if (num_elements != num_sources) {
write_unlock(&filter_lock);
return -EPERM;
}
for (i = 0; i < num_sources; i++) {
pmask[i].isOn =
seemp_logk_get_bit_from_vector(
(__u8 *)buffer, i);
}
write_unlock(&filter_lock);
return 0;
}
static long seemp_logk_set_mapping(unsigned long arg)
{
__u32 num_elements;
__u32 *pbuffer;
int i;
struct seemp_source_mask *pnewmask;
if (copy_from_user(&num_elements,
(__u32 __user *)arg, sizeof(__u32)))
return -EFAULT;
if ((num_elements == 0) || (num_elements >
(UINT_MAX / sizeof(struct seemp_source_mask))))
return -EFAULT;
write_lock(&filter_lock);
if (pmask != NULL) {
/*
* Mask is getting set again.
* seemp_core was probably restarted.
*/
struct seemp_source_mask *ptempmask;
num_sources = 0;
ptempmask = pmask;
pmask = NULL;
kfree(ptempmask);
}
write_unlock(&filter_lock);
pbuffer = kmalloc(sizeof(struct seemp_source_mask)
* num_elements, GFP_KERNEL);
if (pbuffer == NULL)
return -ENOMEM;
/*
* Use our new table as scratch space for now.
* We copy an ordered list of hash values into our buffer.
*/
if (copy_from_user(pbuffer, &((__u32 __user *)arg)[1],
num_elements*sizeof(unsigned int))) {
kfree(pbuffer);
return -EFAULT;
}
/*
* We arrange the user data into a more usable form.
* This is done in-place.
*/
pnewmask = (struct seemp_source_mask *) pbuffer;
for (i = num_elements - 1; i >= 0; i--) {
pnewmask[i].hash = pbuffer[i];
/* Observer is off by default*/
pnewmask[i].isOn = 0;
}
write_lock(&filter_lock);
pmask = pnewmask;
num_sources = num_elements;
write_unlock(&filter_lock);
return 0;
}
static long seemp_logk_check_filter(unsigned long arg)
{
int i;
unsigned int hash = (unsigned int) arg;
/*
* This lock may be a bit long.
* If it is a problem, it can be fixed.
*/
read_lock(&filter_lock);
for (i = 0; i < num_sources; i++) {
if (hash == pmask[i].hash) {
int result = pmask[i].isOn;
read_unlock(&filter_lock);
return result;
}
}
read_unlock(&filter_lock);
return 0;
}
static int seemp_logk_mmap(struct file *filp,
struct vm_area_struct *vma)
{
int ret;
char *vptr;
unsigned long length, pfn;
unsigned long start = vma->vm_start;
length = vma->vm_end - vma->vm_start;
if (length > (unsigned long) slogk_dev->ring_sz) {
pr_err("len check failed\n");
return -EIO;
}
vma->vm_flags |= VM_RESERVED | VM_SHARED;
vptr = (char *) slogk_dev->ring;
ret = 0;
if (kmalloc_flag) {
ret = remap_pfn_range(vma,
start,
virt_to_phys((void *)
((unsigned long)slogk_dev->ring)) >> PAGE_SHIFT,
length,
vma->vm_page_prot);
if (ret != 0) {
pr_err("remap_pfn_range() fails with ret = %d\n",
ret);
return -EAGAIN;
}
} else {
while (length > 0) {
pfn = vmalloc_to_pfn(vptr);
ret = remap_pfn_range(vma, start, pfn, PAGE_SIZE,
vma->vm_page_prot);
if (ret < 0) {
pr_err("remap_pfn_range() fails with ret = %d\n",
ret);
return ret;
}
start += PAGE_SIZE;
vptr += PAGE_SIZE;
length -= PAGE_SIZE;
}
}
return 0;
}
static const struct file_operations seemp_logk_fops = {
.write = seemp_logk_write,
.open = seemp_logk_open,
.unlocked_ioctl = seemp_logk_ioctl,
.compat_ioctl = seemp_logk_ioctl,
.mmap = seemp_logk_mmap,
};
__init int seemp_logk_init(void)
{
int ret;
int devno = 0;
num_sources = 0;
kmalloc_flag = 0;
block_apps = 0;
pmask = NULL;
if (kmalloc_flag && ring_sz > FOUR_MB) {
pr_err("kmalloc cannot allocate > 4MB\n");
return -ENOMEM;
}
ring_sz = ring_sz * SZ_1M;
if (ring_sz <= 0) {
pr_err("Too small a ring_sz=%d\n", ring_sz);
return -EINVAL;
}
slogk_dev = kmalloc(sizeof(*slogk_dev), GFP_KERNEL);
if (slogk_dev == NULL)
return -ENOMEM;
slogk_dev->ring_sz = ring_sz;
slogk_dev->blk_sz = sizeof(struct seemp_logk_blk);
/*initialize ping-pong buffers*/
ret = ringbuf_init(slogk_dev);
if (ret < 0) {
pr_err("Init Failed, ret = %d\n", ret);
goto pingpong_fail;
}
ret = alloc_chrdev_region(&devno, 0, seemp_LOGK_NUM_DEVS,
seemp_LOGK_DEV_NAME);
if (ret < 0) {
pr_err("alloc_chrdev_region failed with ret = %d\n",
ret);
goto register_fail;
}
slogk_dev->major = MAJOR(devno);
pr_debug("logk: major# = %d\n", slogk_dev->major);
cl = class_create(THIS_MODULE, seemp_LOGK_DEV_NAME);
if (cl == NULL) {
pr_err("class create failed");
goto cdev_fail;
}
if (device_create(cl, NULL, devno, NULL,
seemp_LOGK_DEV_NAME) == NULL) {
pr_err("device create failed");
goto class_destroy_fail;
}
cdev_init(&(slogk_dev->cdev), &seemp_logk_fops);
slogk_dev->cdev.owner = THIS_MODULE;
ret = cdev_add(&(slogk_dev->cdev), MKDEV(slogk_dev->major, 0), 1);
if (ret) {
pr_err("cdev_add failed with ret = %d", ret);
goto class_destroy_fail;
}
seemp_logk_attach();
mutex_init(&slogk_dev->lock);
init_waitqueue_head(&slogk_dev->readers_wq);
init_waitqueue_head(&slogk_dev->writers_wq);
rwlock_init(&filter_lock);
return 0;
class_destroy_fail:
class_destroy(cl);
cdev_fail:
unregister_chrdev_region(devno, seemp_LOGK_NUM_DEVS);
register_fail:
ringbuf_cleanup(slogk_dev);
pingpong_fail:
kfree(slogk_dev);
return -EPERM;
}
__exit void seemp_logk_cleanup(void)
{
dev_t devno = MKDEV(slogk_dev->major, slogk_dev->minor);
seemp_logk_detach();
cdev_del(&slogk_dev->cdev);
unregister_chrdev_region(devno, seemp_LOGK_NUM_DEVS);
ringbuf_cleanup(slogk_dev);
kfree(slogk_dev);
if (pmask != NULL) {
kfree(pmask);
pmask = NULL;
}
}
module_init(seemp_logk_init);
module_exit(seemp_logk_cleanup);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("seemp Observer");