Skip to content

Instantly share code, notes, and snippets.

@RavuAlHemio
Created January 19, 2014 23:55
Show Gist options
  • Save RavuAlHemio/8512684 to your computer and use it in GitHub Desktop.
Save RavuAlHemio/8512684 to your computer and use it in GitHub Desktop.
secvault kernel module
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/thread_info.h>
#include <linux/sched.h>
#include "../svinterface.h"
/*
CONSTANT DECLARATIONS
*/
/** Maximum number of vaults to supply. */
#define MAX_NUM_VAULTS 4
/** Maximum size of one vault. */
#define MAX_VAULT_SIZE 1048576
/** Name format (%zu) of the vaults themselves. */
#define DEVNAME_VAULT "sv_data%zu"
/*
MODULE METADATA
*/
MODULE_LICENSE("Dual MIT/GPL"); /* quite possibly the most liberal combination */
MODULE_AUTHOR("Ondrej Hosek <[email protected]>");
MODULE_DESCRIPTION("Secure Vault");
MODULE_VERSION("0.0.1");
/*
MODULE PARAMETERS
*/
static int debug;
module_param(debug, bool, 0);
MODULE_PARM_DESC(debug, "Log debugging messages to dmesg.");
/*
FUNCTION PROTOTYPES
*/
static int io_succeed(struct inode *n, struct file *f);
static long svctl_ioctl(struct file *f, unsigned int num, unsigned long param);
static int secvault_open(struct inode *n, struct file *f);
ssize_t secvault_read(struct file *filp, char *buf, size_t count, loff_t *f_pos);
ssize_t secvault_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos);
loff_t secvault_llseek(struct file *f, loff_t offset, int whence);
static int take_secvault_offline(size_t vaultnum);
static int take_secvault_online(size_t vaultnum, size_t size, char key[SECVAULT_KEY_LEN]);
/*
STRUCTURES
*/
/** File operations for the controller device. */
struct file_operations svctl_fops = {
.owner = THIS_MODULE,
.open = io_succeed,
.release = io_succeed,
.unlocked_ioctl = svctl_ioctl
};
/** File operations for the vault devices. */
struct file_operations secvault_fops = {
.owner = THIS_MODULE,
.open = secvault_open,
.release = io_succeed,
.read = secvault_read,
.write = secvault_write,
.llseek = secvault_llseek
};
/** Metadata holder for the controller device. */
struct svctl_dev
{
/** Character device info. */
struct cdev cdev;
/** Kernel-created device file in sysfs and devfs. */
struct device *kdevice;
/** 1 if the controller device is registered, 0 if not; any other value is junk */
char registered;
/** Semaphore for control access. */
struct semaphore sema;
};
/** Metadata holder for a vault device. */
struct secvault_dev
{
/** Character device info. */
struct cdev cdev;
/** Kernel-created device file in sysfs and devfs. */
struct device *kdevice;
/** 1 if the vault device is registered, 0 if not; any other value is junk */
char registered;
/** 1 if this vault is currently online, 0 if not; any other value is junk. */
char online;
/** Semaphore for vault access. */
struct semaphore sema;
/** Vault key. */
char key[SECVAULT_KEY_LEN];
/** Current size of the vault. */
size_t size;
/** Pointer to vault contents. */
char *contents;
/** Owner of the vault. */
uid_t owner;
};
/*
MODULE SCOPE VARIABLES
*/
/** First device number used. */
dev_t devnumber1;
/** The control device. */
struct svctl_dev ctldev;
/** The individual vault devices. */
struct secvault_dev vaultdevs[MAX_NUM_VAULTS];
/** Device class for secvault in sysfs. */
struct class *secvault_class;
/*
UTILITY FUNCTIONS
*/
/**
* printk if debugging is on.
* @param fmt Format string.
* @return Number of bytes printed.
*/
static __attribute__((format(printf, 1, 2))) int dprintk(const char *fmt, ...)
{
va_list args;
int r;
if (debug == 0)
return 0;
va_start(args, fmt);
r = vprintk(fmt, args);
va_end(args);
return r;
}
/**
* Na&#239;ve implementation of memcpy.
* @param dest Destination pointer.
* @param src Source pointer.
* @param n Number of bytes to copy from src to dest.
* @return dest
*/
static inline void *mymemcpy(void *dest, const void *src, size_t n)
{
__u8 *d = (__u8 *)dest;
const __u8 *s = (const __u8 *)src;
size_t i;
for (i = 0; i < n; ++i)
{
d[i] = s[i];
}
return dest;
}
/**
* Na&#239;ve implementation of memset.
* @param s Pointer to memory to set.
* @param c Character to set to. Truncated to 8 bits.
* @param n Number of bytes to set.
* @return s
*/
static inline void *mymemset(void *s, int c, size_t n)
{
__u8 *d = (__u8 *)s;
__u8 b = (__u8)c;
size_t i;
for (i = 0; i < n; ++i)
{
d[i] = b; /* ZIIIIM! */
}
return s;
}
/**
* Convert device number to vault ID.
* @param devnum Device number.
* @return Ordinal vault ID.
*/
static inline size_t device_to_vault(dev_t devnum)
{
return devnum - devnumber1 - 1; /* skip the control device */
}
/*
GENERAL DEVICE I/O
*/
/**
* open() and release() call that does nothing and returns successfully.
*/
static int io_succeed(struct inode *n, struct file *f)
{
/* nothing to see here, move along */
return 0;
}
/*
VAULT DEVICE I/O
*/
/**
* Open a vault (storing its ID in the file structure).
*/
static int secvault_open(struct inode *n, struct file *f)
{
size_t vaultnum = device_to_vault(n->i_rdev);
struct secvault_dev *v;
dprintk(
KERN_INFO "%s: Opening vault device 0x%x; devnumber1 is 0x%x; effective number is 0x%zx.",
__func__,
n->i_rdev,
devnumber1,
vaultnum
);
if (vaultnum >= MAX_NUM_VAULTS)
{
printk(KERN_ERR "%s: Vault %zu out of bounds.", __func__, vaultnum);
return -ENODEV;
}
v = &vaultdevs[vaultnum];
if (v->online != 1)
{
dprintk(
KERN_ERR "%s: Vault %zu is not online.",
__func__,
vaultnum
);
return -ENODEV;
}
if (down_interruptible(&v->sema) == -EINTR)
{
return -ERESTARTSYS;
}
if (
v->owner != current_uid() &&
v->owner != current_euid() &&
!capable(CAP_DAC_OVERRIDE)
)
{
/* access denied */
dprintk(
KERN_INFO "%s: Access denied to UID %u EUID %u to vault %zu with owner %u.",
__func__,
current_uid(),
current_euid(),
vaultnum,
v->owner
);
up(&v->sema);
return -EPERM;
}
up(&v->sema);
f->private_data = (void *)vaultnum;
return 0;
}
/**
* Read from a vault.
*/
ssize_t secvault_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
size_t vaultnum = (size_t)filp->private_data;
size_t vaultsize, i, endpos, bytesread = 0;
struct secvault_dev *v;
char *kernbuf;
/* sanity checking */
if (vaultnum >= MAX_NUM_VAULTS)
{
printk(KERN_ERR "%s: Vault %zu out of bounds.", __func__, vaultnum);
return -ENODEV;
}
v = &vaultdevs[vaultnum];
if (v->online != 1)
{
dprintk(KERN_ERR "%s: Vault %zu unavailable.", __func__, vaultnum);
return -ENODEV;
}
if (down_interruptible(&v->sema) == -EINTR)
{
return -ERESTARTSYS;
}
/* check position */
vaultsize = v->size;
if (*f_pos >= vaultsize)
{
/* reached EOF */
up(&v->sema);
return 0;
}
/* allocate buffer */
endpos = *f_pos + count;
kernbuf = kmalloc(count, GFP_KERNEL);
if (IS_ERR(kernbuf))
{
printk(KERN_ERR "%s: Cannot allocate buffer.", __func__);
up(&v->sema);
return -ENOMEM;
}
for (i = *f_pos; i < vaultsize && i < endpos; ++i)
{
/* decryption routine */
kernbuf[i] = v->contents[i] ^ v->key[i % 10];
++bytesread;
}
/* copy buffer to userspace */
if (copy_to_user(
buf,
kernbuf,
bytesread
) != 0)
{
printk(KERN_ERR "%s: Failed to copy bytes.", __func__);
kfree(kernbuf);
up(&v->sema);
return -ENOMEM;
}
kfree(kernbuf);
up(&v->sema);
*f_pos += bytesread;
return bytesread;
}
/**
* Write to a vault.
*/
ssize_t secvault_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
size_t vaultnum = (size_t)filp->private_data;
size_t vaultsize, i, endpos, byteswritten = 0;
struct secvault_dev *v;
char *kernbuf;
/* sanity checking */
if (vaultnum >= MAX_NUM_VAULTS)
{
printk(KERN_ERR "%s: Vault %zu out of bounds.", __func__, vaultnum);
return -ENODEV;
}
v = &vaultdevs[vaultnum];
if (v->online != 1)
{
dprintk(KERN_ERR "%s: Vault %zu unavailable.", __func__, vaultnum);
return -ENODEV;
}
if (down_interruptible(&v->sema) == -EINTR)
{
return -ERESTARTSYS;
}
/* check position */
vaultsize = v->size;
if (*f_pos >= vaultsize)
{
/* no space left */
up(&v->sema);
return -ENOSPC;
}
/* allocate buffer */
endpos = *f_pos + count;
kernbuf = kmalloc(count, GFP_KERNEL);
if (IS_ERR(kernbuf))
{
printk(KERN_ERR "%s: Cannot allocate buffer.", __func__);
up(&v->sema);
return -ENOMEM;
}
/* copy buffer from userspace */
if (copy_from_user(
kernbuf,
buf,
count
) != 0)
{
printk(KERN_ERR "%s: Failed to copy bytes.", __func__);
kfree(kernbuf);
up(&v->sema);
return -ENOMEM;
}
for (i = *f_pos; i < vaultsize && i < endpos; ++i)
{
/* encryption routine */
v->contents[i] = kernbuf[i] ^ v->key[i % 10];
++byteswritten;
}
kfree(kernbuf);
up(&v->sema);
*f_pos += byteswritten;
return byteswritten;
}
/**
* Seek within a vault. Code mostly stolen from default_llseek.
*/
loff_t secvault_llseek(struct file *f, loff_t offset, int whence)
{
size_t vaultnum = (size_t)f->private_data;
struct secvault_dev *v;
loff_t newoff = -1;
/* sanity checking */
if (vaultnum >= MAX_NUM_VAULTS)
{
printk(KERN_ERR "%s: Vault %zu out of bounds.", __func__, vaultnum);
return -ENODEV;
}
v = &vaultdevs[vaultnum];
if (v->online != 1)
{
dprintk(KERN_ERR "%s: Vault %zu unavailable.", __func__, vaultnum);
return -ENODEV;
}
if (down_interruptible(&v->sema) == -EINTR)
{
return -ERESTARTSYS;
}
switch (whence)
{
case SEEK_SET:
newoff = offset;
break;
case SEEK_END:
newoff = offset + v->size;
break;
case SEEK_CUR:
newoff = offset + f->f_pos;
break;
#ifdef SEEK_DATA
case SEEK_DATA:
if (offset > v->size)
{
up(&v->sema);
return -ENXIO;
}
newoff = offset;
break;
#endif
#ifdef SEEK_HOLE
case SEEK_HOLE:
if (offset > v->size)
{
up(&v->sema);
return -ENXIO;
}
newoff = v->size;
break;
#endif
}
if (newoff >= 0)
{
if (newoff != f->f_pos)
{
f->f_pos = newoff;
f->f_version = 0;
}
up(&v->sema);
return newoff;
}
up(&v->sema);
return -EINVAL;
}
/*
CONTROL DEVICE I/O
*/
/**
* ioctl on control file.
*/
static long svctl_ioctl(struct file *f, unsigned int num, unsigned long param)
{
switch (num)
{
case SECVAULT_IOC_CREATE:
{
struct secvault_iocs_create paramstruct;
if (copy_from_user(
&paramstruct,
(const void __user *)param,
sizeof(paramstruct)
) != 0)
{
printk(
KERN_WARNING "%s: Failed to copy bytes from ioctl param.",
__func__
);
return -EFAULT;
}
return take_secvault_online(paramstruct.vaultnum, paramstruct.size, paramstruct.key);
}
case SECVAULT_IOC_CLEAR:
{
size_t vaultnum = (size_t)param;
struct secvault_dev *v;
if (vaultnum >= MAX_NUM_VAULTS)
{
dprintk(
KERN_WARNING "%s: Tried to clear vault %zu; the highest vault number is %d.",
__func__,
vaultnum,
MAX_NUM_VAULTS-1
);
return -ENODEV;
}
v = &vaultdevs[vaultnum];
if (v->online != 1)
{
dprintk(
KERN_WARNING "%s: Vault %zu is currently not online.\n",
__func__,
vaultnum
);
return -ENODEV;
}
if (down_interruptible(&v->sema) == -EINTR)
{
return -ERESTARTSYS;
}
mymemset(v->contents, 0x00, v->size);
up(&v->sema);
return 0;
}
case SECVAULT_IOC_REMOVE:
return take_secvault_offline((size_t)param);
default:
dprintk(
KERN_WARNING "%s: Unknown ioctl %u on control file.",
__func__,
num
);
return -ENOIOCTLCMD;
}
return -ENOIOCTLCMD;
}
/*
VAULT MANAGEMENT
*/
/**
* Take the specified secvault offline.
* @param vaultnum Number of the vault to take online.
* @return Zero on success, negative number on error.
*/
static int take_secvault_offline(size_t vaultnum)
{
struct secvault_dev *v;
if (down_interruptible(&ctldev.sema) == -EINTR)
{
return -ERESTARTSYS;
}
if (vaultnum >= MAX_NUM_VAULTS)
{
dprintk(
KERN_WARNING "%s: Tried to take vault %zu offline; the highest vault number is %d.",
__func__,
vaultnum,
MAX_NUM_VAULTS-1
);
up(&ctldev.sema);
return -ENODEV;
}
v = &vaultdevs[vaultnum];
if (v->online)
{
if (down_interruptible(&v->sema) == -EINTR)
{
up(&ctldev.sema);
return -ERESTARTSYS;
}
}
/* alright, let's do this */
v->online = 0;
if (v->kdevice != NULL)
{
dprintk(KERN_DEBUG "%s: destroying device for vault %zu.", __func__, vaultnum);
device_destroy(secvault_class, devnumber1+1+vaultnum);
v->kdevice = NULL;
}
if (v->registered == 1)
{
dprintk(KERN_DEBUG "%s: deleting device for vault %zu.", __func__, vaultnum);
cdev_del(&v->cdev);
v->registered = 0;
}
if (v->contents != NULL)
{
/* zero out memory before freeing */
(void)mymemset(v->contents, 0x00, v->size);
}
#ifdef DATA_USING_KMALLOC
kfree(v->contents);
#else
vfree(v->contents);
#endif
dprintk(KERN_DEBUG "%s: freeing space of vault %zu.", __func__, vaultnum);
v->contents = NULL;
v->size = 0;
mymemset(v->key, 0x00, SECVAULT_KEY_LEN);
dprintk(KERN_INFO "%s: Vault %zu taken offline.", __func__, vaultnum);
up(&ctldev.sema);
return 0;
}
/**
* Take the specified secvault online.
* @param vaultnum Number of the vault to take online.
* @param size Size, in bytes, of the new vault.
* @param key Key to access vault.
* @return Zero on success, negative number on error.
*/
static int take_secvault_online(size_t vaultnum, size_t size, char key[SECVAULT_KEY_LEN])
{
dev_t mydevnumber;
struct secvault_dev *v;
if (down_interruptible(&ctldev.sema) == -EINTR)
{
return -ERESTARTSYS;
}
if (vaultnum >= MAX_NUM_VAULTS)
{
dprintk(
KERN_WARNING "%s: Tried to take vault %zu online; the highest vault number is %d.",
__func__,
vaultnum,
MAX_NUM_VAULTS-1
);
up(&ctldev.sema);
return -ENODEV;
}
if (vaultdevs[vaultnum].online == 1)
{
dprintk(
KERN_WARNING "%s: Tried to take vault %zu online; it already is.",
__func__,
vaultnum
);
up(&ctldev.sema);
return -EEXIST;
}
else if (vaultdevs[vaultnum].online != 0)
{
dprintk(
KERN_ERR "%s: Vault %zu online state (%hhu) unknown.",
__func__,
vaultnum,
vaultdevs[vaultnum].online
);
up(&ctldev.sema);
return -EBUSY;
}
#if MAX_VAULT_SIZE != 0
if (size > MAX_VAULT_SIZE)
{
dprintk(
KERN_WARNING "%s: Tried to create vault %zu bytes long; the maximum allowed size is %d bytes.",
__func__,
size,
MAX_VAULT_SIZE
);
up(&ctldev.sema);
return -EFBIG;
}
#endif
/* okay, sanity checking's done; create the vault. */
v = &vaultdevs[vaultnum];
/* init semaphore */
sema_init(&v->sema, 0);
/* copy key */
(void)mymemcpy(v->key, key, SECVAULT_KEY_LEN);
/* copy size */
v->size = size;
/* set owner */
v->owner = current_uid();
/* allocate contents */
#ifdef DATA_USING_KMALLOC
v->contents = kmalloc(v->size, GFP_KERNEL);
#else
v->contents = vmalloc(v->size);
#endif
dprintk(KERN_DEBUG "%s: allocating space for vault %zu.", __func__, vaultnum);
if (IS_ERR(v->contents))
{
printk(
KERN_ERR "%s: Failed to allocate %zu bytes for vault %zu.",
__func__,
v->size,
vaultnum
);
up(&ctldev.sema);
up(&v->sema);
take_secvault_offline(vaultnum);
return -ENOMEM;
}
mydevnumber = devnumber1 + 1 + vaultnum;
/* prepare the vault device */
dprintk(KERN_DEBUG "%s: adding device for vault %zu.", __func__, vaultnum);
cdev_init(&v->cdev, &secvault_fops);
if (cdev_add(&v->cdev, mydevnumber, 1) != 0)
{
printk(
KERN_ERR "%s: Failed to add vault %zu device to system.",
__func__,
vaultnum
);
up(&ctldev.sema);
up(&v->sema);
take_secvault_offline(vaultnum);
return -ENODEV;
}
v->registered = 1;
/* create the ctl device file */
dprintk(KERN_DEBUG "%s: creating device for vault %zu.", __func__, vaultnum);
v->kdevice = device_create(
secvault_class, /* class */
NULL, /* parent */
mydevnumber, /* device number */
NULL, /* additional data for callbacks */
DEVNAME_VAULT, /* name (format) */
vaultnum
);
if (v->kdevice == NULL)
{
printk(
KERN_ERR "%s: Failed to create vault %zu device file.",
__func__,
vaultnum
);
up(&ctldev.sema);
up(&v->sema);
take_secvault_offline(vaultnum);
return -ENODEV;
}
dprintk(KERN_INFO "%s: Vault %zu taken online.", __func__, vaultnum);
v->online = 1;
up(&ctldev.sema);
up(&v->sema);
return 0;
}
/*
MODULE HOUSEKEEPING
*/
/** Exit the module. */
static void secvault_mod_exit(void)
{
size_t i;
for (i = 0; i < MAX_NUM_VAULTS; ++i)
{
while (take_secvault_offline(i) == -ERESTARTSYS)
;
}
if (ctldev.kdevice != NULL)
{
device_destroy(secvault_class, devnumber1);
ctldev.kdevice = NULL;
}
if (ctldev.registered == 1)
{
cdev_del(&ctldev.cdev);
ctldev.registered = 0;
}
if (devnumber1 != 0)
{
unregister_chrdev_region(devnumber1, MAX_NUM_VAULTS+1);
devnumber1 = 0;
}
if (secvault_class != NULL)
{
class_destroy(secvault_class);
secvault_class = NULL;
}
}
/**
* Initialize the secvault module.
* @return Zero on success, nonzero on failure (with negative error number).
*/
static int secvault_mod_init(void)
{
int ret = 0;
size_t i = 0;
/* clear variables. */
devnumber1 = 0;
memset(&ctldev, 0x00, sizeof(ctldev));
for (i = 0; i < MAX_NUM_VAULTS; ++i)
{
memset(&vaultdevs[i], 0x00, sizeof(struct secvault_dev));
}
secvault_class = NULL;
/* register device class */
secvault_class = class_create(THIS_MODULE, "secvault");
if (IS_ERR(secvault_class))
{
printk(KERN_ERR "%s: Failed to register secvault device class.", __func__);
ret = -ENODEV;
goto kablooie;
}
/* obtain major/minor device numbers */
if (alloc_chrdev_region(&devnumber1, 0, MAX_NUM_VAULTS+1, "secvault") != 0)
{
printk(KERN_ERR "%s: Failed to allocate character device numbers.", __func__);
ret = -ENODEV;
goto kablooie;
}
/* create ctl device semaphore */
sema_init(&ctldev.sema, 0);
/* prepare the ctl device */
cdev_init(&ctldev.cdev, &svctl_fops);
if (cdev_add(&ctldev.cdev, devnumber1, 1) != 0)
{
printk(KERN_ERR "%s: Failed to add controller device to system.", __func__);
ret = -ENODEV;
goto kablooie;
}
ctldev.registered = 1;
/* create the ctl device file */
ctldev.kdevice = device_create(
secvault_class, /* class */
NULL, /* parent */
devnumber1, /* device number */
NULL, /* additional data for callbacks */
SECVAULT_DEVNAME_CTL /* name (format) */
);
if (ctldev.kdevice == NULL)
{
printk(KERN_ERR "%s: Failed to create controller device file.", __func__);
ret = -ENODEV;
goto kablooie;
}
/* unlock the ctl device semaphore */
up(&ctldev.sema);
dprintk(KERN_INFO "%s: Ready.", __func__);
return ret;
kablooie:
/* do standard teardown, which is relatively robust */
secvault_mod_exit();
return ret;
}
/*
ENTRY POINT DECLARATIONS
*/
module_init(secvault_mod_init);
module_exit(secvault_mod_exit);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment