new module to check constant memory for corruption
From: Alexander . Kleinsorge
Date: Sat Apr 12 2014 - 21:33:24 EST
ramcheck kernel module
new module to check constant memory for corruption
detect corruption of constant kernel memory (text and data) periodically.
runtime costs about 1..2 ms per sec (about 10 mb with 5 mb/ms),
which is distributed over 8 (BLOCKS) time partitions (less than half ms per sec).
in case of checksum (xor) error, an kernel log is posted.
manual trigger via /proc/ramcheck is possible.
range: kallsyms_lookup_name("_text") .. kallsyms_lookup_name("__end_rodata")
this new module helps against two scenarios:
1) bit flip on system without crc (typical pc)
more often than you think:
DRAM Errors in the Wild: A Large-Scale Field Study, Mai 2009.
http://www.cs.toronto.edu/~bianca/papers/sigmetrics09.pdf
2) architectures without memory protection plus attack or out-of-bound-writes.
x86 and x64 using it (write protection), but missing (or not enabled) on most other arch.
see: ramcheck.tgz
Attachment:
ramcheck.tgz
Description: Binary data
/* ramcheck.c - checks predefined kernel memory region
*
* KernelRamCheck by
*
* Copyright 2014 Alexander Kleinsorge <alexander[dot]kleinsorge[at]gmx[dot]de>
* Copyright 2014 Benjamin Schroedl <benjamin[at]dev-tec[dot]de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
// Defining __KERNEL__ and MODULE allows us to access kernel-level code not usually available to userspace programs.
/*
#undef __KERNEL__
#define __KERNEL__
#undef MODULE
#define MODULE
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>
#include <linux/pfn.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <crypto/algapi.h>
#include <asm/uaccess.h>
#include <asm/sections.h>
#include <linux/time.h>
#define DRIVER_AUTHOR "Alexander Kleinsorge <alexander[dot]kleinsorge[at]gmx[dot]de> and Benjamin Schroedl <benjamin[at]dev-tec[dot]de>"
#define DRIVER_DESC "checks predefined memory kernel region"
#define proc_fs_name "ramcheck"
#define MODNAME proc_fs_name" "
#define CHECK_TIMER_MS 1000
#define BLOCK ((sizeof(long) <= 4) ? 1u << 20 : 2u << 20) /* 64bit CPU is often faster (bigger blocks at once) */
#define BLOCKS 8
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
static void ram_check_exit(void);
static int ram_check_init(void);
static int ram_check_print(struct seq_file *m, void *v);
static int ram_check_run(void);
void ram_check_timer_callback(unsigned long data);
static int ram_check_open(struct inode *inode, struct file *file);
static struct timer_list ram_check_timer;
struct proc_dir_entry *ram_check_proc_file;
struct file_operations proc_fops = {
.open = ram_check_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
/* Global Variables */
static unsigned long g_RangeSize = 0;
static unsigned long g_RangeStart = 0;
static unsigned int g_TempCount = 0;
static unsigned int g_Timers = 0;
static unsigned int g_TimerActive = 0;
static unsigned long g_SumFirst = 0;
static unsigned long g_SumLast = 0;
static unsigned long g_BlockSum[BLOCKS];
/* Functions */
/* simple checksum/hash, xor is fastest (~1-6 MB/ms) */
static unsigned long calcChecksumXor(const void* ptr, const unsigned long bytes)
{
unsigned long ret, i;
const unsigned long long* ptr64 = (const unsigned long long*) ptr;
const unsigned long len = bytes / sizeof(*ptr64);
unsigned long long sum64 = 0u;
for (i=0; i < len; i++) {
sum64 ^= ptr64[i];
}
/* return 32 or 64 bit possible */
ret = ((unsigned long) sum64) ^ ((unsigned long) (sum64>>32));
return ret;
}
/* merge 4 to 1 byte ==> less info (security reason) */
static unsigned char shrink2byte(unsigned long u)
{
unsigned long ret = (u) ^ (u>>8) ^ (u>>16) ^ (u>>24);
/* (lowest) 32 bit --> 8 bit */
return (unsigned char) ret;
}
static unsigned long long getTimeUS(void)
{
struct timespec ts;
ktime_get_ts( &ts );
return (ts.tv_sec*USEC_PER_SEC) + (ts.tv_nsec/1000u);
}
// check for const range (in case of strange checksum)
static int check_range(void)
{
unsigned long start = kallsyms_lookup_name("_text");
unsigned long size = kallsyms_lookup_name("__end_rodata") - start;
int ret = 0;
if (g_RangeSize && ((start != g_RangeStart) || (size != g_RangeSize))) {
printk(KERN_WARNING MODNAME "error: const kernel memory address changed (%08lx + %lu K), stop!\n", start, size/1024u);
g_RangeSize = 0; /* stop working */
ret = -1;
}
return ret;
}
/* partitions to limit runtime to 1 ms (1..6 MB) */
static unsigned long check_block(unsigned long blk, unsigned long lastblk)
{
const unsigned long offset = blk * BLOCK;
unsigned long start = g_RangeStart + offset;
unsigned long size = BLOCK;
unsigned long ret;
if (blk >= lastblk) {
size = g_RangeSize % BLOCK;
if (blk > lastblk) {
return (~0u); /* should never happen */
}
}
ret = calcChecksumXor((const void *) start, size);
return ret;
}
static int ram_check_init(void)
{
int ret;
unsigned long start, size, end;
unsigned long b, t, bps;
for (b=0; b<BLOCKS; b++) {
g_BlockSum[b] = 0u;
}
ram_check_proc_file = proc_create(proc_fs_name, 0, NULL, &proc_fops);
if (ram_check_proc_file == NULL) {
remove_proc_entry(proc_fs_name, NULL);
printk(KERN_ALERT MODNAME "error: Could not initialize /proc/%s\n", proc_fs_name);
return -ENOMEM;
}
start = kallsyms_lookup_name("_text");
end = kallsyms_lookup_name("__end_rodata");
/* kallsyms_lookup_name("__end_rodata_hpage_align"); ==> Bad Address! */
size = (end - start);
/* error handling on illegal size and ptr or size to big */
if ((start == 0) || (end == 0) || (end <= start) || (size > (1000u << 20))) {
printk(KERN_ALERT MODNAME "error: bad start or end adress [%lx + %lx]\n", start, size);
return -EFAULT;
}
if ((size < (1u << 20)) || (size > (200u << 20))) {
printk(KERN_INFO MODNAME "warning: strange const size = %lu MB\n", size>>20);
}
g_RangeStart = start;
g_RangeSize = size;
t = (unsigned long) getTimeUS();
for (b = 0; b <= size/BLOCK; b++) {
g_BlockSum[b % BLOCKS] ^= check_block(b, size/BLOCK);
}
t = (unsigned long) getTimeUS() - t; /* ca. 1k..7k Byte/us */
for (b = 0; b < BLOCKS; b++) {
g_SumFirst ^= g_BlockSum[b]; /* all together */
}
bps = (t > 0) ? (size / t) : 0;
printk(KERN_INFO MODNAME "init: %lu B/us, %lu kB, %d bit, (0x%02x, %u)\n",
bps, size>>10, (int)sizeof(g_SumFirst)*8, shrink2byte(g_SumFirst), BLOCKS);
/* TIMER init */
setup_timer(&ram_check_timer, ram_check_timer_callback, 0);
ret = mod_timer(&ram_check_timer, jiffies + msecs_to_jiffies(CHECK_TIMER_MS));
if (ret) {
printk(KERN_ALERT MODNAME "error: Failure in mod_timer\n");
return -EFAULT;
}
g_TimerActive = 1;
return 0;
}
static void ram_check_exit(void)
{
int ret;
g_RangeSize = 0; /* stop work */
remove_proc_entry(proc_fs_name, NULL);
check_range();
for (ret = 0; (g_TimerActive > 0) && (ret < 1000/20); ret ++) {
mdelay(20);
}
ret = del_timer_sync(&ram_check_timer);
if (ret) {
printk(KERN_WARNING MODNAME "warning: timer still in use on exit...(%d,%u)\n", ret, g_Timers);
} else {
printk(KERN_INFO MODNAME ": exit (%lx,%u)\n", g_SumLast, g_Timers);
}
}
/* file handler /proc/.. */
static int ram_check_print(struct seq_file *m, void *v)
{
int ret = 0;
unsigned long t = 0;
ret = check_range();
if (ret) {
seq_printf(m, "ERROR: const kernel memory address changed (%08lx), stop!\n", g_SumLast);
return -1;
}
if (g_SumLast != 0u) {
seq_printf(m, "ERROR: const kernel memory was earlier broken (%08lx), no check again!\n", g_SumLast);
return -2;
}
if (g_TempCount <= 2) { /* max 2 calls per timer (1 sec) */
unsigned long xor;
g_TempCount ++;
t = (unsigned long) getTimeUS();
xor = calcChecksumXor((const void *) g_RangeStart, g_RangeSize);
t = (unsigned long) getTimeUS() - t;
xor = g_SumFirst ^ xor;
if (xor) g_SumLast = xor; /* only write bad events */
} else {
g_TempCount ++;
seq_printf(m, "warning: too much triggers (%u) for ram_check (%08lx) !\n", g_TempCount, g_SumLast);
}
if (!g_SumLast) {
seq_printf(m, "const kernel memory is OK, size = %lu MB, t = %lu us (%u)\n", g_RangeSize>>20, t, g_Timers);
} else {
printk(KERN_EMERG MODNAME "error: const kernel memory is broken (%08lx != 0), please reboot!", g_SumLast);
seq_printf(m, "ERROR: const kernel memory is broken (%08lx != 0), please reboot!\n", g_SumLast);
ret = -3;
/* !! kernel panic (ram error) !! */
}
return ret;
}
/* only inside timer (1 sec) */
static int ram_check_run(void)
{
const unsigned int lastblk = (unsigned int) (g_RangeSize / BLOCK);
const unsigned int b = g_Timers % BLOCKS;
unsigned int l;
unsigned long x = 0;
if (0 == b) {
check_range(); /* check sometimes */
}
for (l = b; l <= lastblk; l += BLOCKS) {
x ^= check_block(l, lastblk);
}
x ^= g_BlockSum[b];
if (x != 0u) {
check_range();
g_SumLast = x;
/* !! kernel panic (ram error) !! */
printk(KERN_EMERG MODNAME "error: const kernel memory broken (%08lx != 0), timer(%u, %u) !", x, g_Timers, b);
}
return 0;
}
/* Timer Callback */
void ram_check_timer_callback(unsigned long data)
{
int ret;
g_Timers ++;
if (0 == g_RangeSize) {
g_TimerActive = 0;
return;
}
if (g_SumLast != 0u) { /* failure earlier detected, skip */
if (0 == (g_Timers % 32u)) {
printk(KERN_WARNING MODNAME "error: const kernel memory earlier broken (%08lx), skip !\n", g_SumLast);
}
return;
}
ram_check_run();
ret = mod_timer(&ram_check_timer, jiffies + msecs_to_jiffies(CHECK_TIMER_MS));
if (ret) {
printk(KERN_ALERT MODNAME "error: in mod_timer (%u, %d)\n", g_Timers, ret);
}
g_TimerActive = 1; /* is already set, but be safe */
g_TempCount = 0;
}
static int ram_check_open(struct inode *inode, struct file *file)
{
return single_open(file, ram_check_print, NULL);
}
module_init(ram_check_init);
module_exit(ram_check_exit);