mirror of
https://github.com/chipsec/chipsec
synced 2026-06-08 13:31:00 +00:00
4599981389
Currently Linux driver uses `memcpy` when reading physical memory. This causes a kernel panic on some systems when trying to access "forbidden" physical memory. The Linux kernel provides function `copy_from_kernel_nofault` (previously named `probe_kernel_read`) to read memory without crashing (and this function is used by the implementation of `/dev/mem`). Replace `memcpy` with `copy_from_kernel_nofault`, providing a simple wrapper for older kernels. While at it: - Move the allocation of a bounce buffer outside of the while loop. As memory is always read one page at a time, a `PAGE_SIZE` buffer is enough. - Use `size_t` as the type of `sz` instead of the signed type `ssize_t`. - Replace calls to `min_t` with `if` statements which are easier to read. - Name the number of written bytes `written` instead of `read`, in `write_mem`. - Remove stray spaces in various places. By the way, the `write_mem` function was not updated to use `copy_to_kernel_nofault`, as this function has no longer been exported since Linux 5.8. Fixes: https://github.com/chipsec/chipsec/issues/1094 Signed-off-by: Nicolas Iooss <nicolas.iooss_git@polytechnique.org>
2004 lines
52 KiB
C
2004 lines
52 KiB
C
/*
|
|
CHIPSEC: Platform Security Assessment Framework
|
|
Copyright (c) 2010-2020, Intel Corporation
|
|
|
|
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; Version 2.
|
|
|
|
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
Contact information:
|
|
chipsec@intel.com
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/highmem.h>
|
|
#include <linux/kallsyms.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/version.h>
|
|
#include <linux/slab.h>
|
|
#include <asm/io.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/miscdevice.h>
|
|
|
|
#include "include/chipsec.h"
|
|
|
|
#ifdef CONFIG_EFI
|
|
#include <linux/efi.h>
|
|
#endif
|
|
|
|
|
|
#define CHIPSEC_VER_ 1
|
|
#define CHIPSEC_VER_MINOR 2
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25)
|
|
/* 'ioremap_nocache' was deprecated in kernels >= 5.6, so instead we use 'ioremap' which
|
|
is no-cache by default since kernels 2.6.25. */
|
|
# define IOREMAP_NO_CACHE(address, size) ioremap(address, size)
|
|
#else /* KERNEL_VERSION < 2.6.25 */
|
|
# define IOREMAP_NO_CACHE(address, size) ioremap_nocache(address, size)
|
|
#endif
|
|
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,10,0)
|
|
#include <linux/static_call.h>
|
|
#include <linux/kprobes.h>
|
|
|
|
static unsigned long chipsec_lookup_name_scinit(const char *name);
|
|
static int chipsec_page_is_ram_scinit(unsigned long pagenr);
|
|
DEFINE_STATIC_CALL(chipsec_lookup_name_sc, chipsec_lookup_name_scinit);
|
|
DEFINE_STATIC_CALL(chipsec_page_is_ram_sc, chipsec_page_is_ram_scinit);
|
|
#endif
|
|
|
|
// function page_is_ram is not exported
|
|
// for modules, but is available in kallsyms.
|
|
// So we need determine this address using dirty tricks
|
|
static int (*guess_page_is_ram)(unsigned long pagenr);
|
|
static int chipsec_page_is_ram(unsigned long pagenr);
|
|
// same with phys_mem_accesss_prot
|
|
pgprot_t (*guess_phys_mem_access_prot)(struct file *file, unsigned long pfn,
|
|
unsigned long size, pgprot_t vma_prot);
|
|
|
|
unsigned long a1=0;
|
|
unsigned long a2=0;
|
|
module_param(a1,ulong,0); //a1 is addr of page_is_ram function
|
|
module_param(a2,ulong,0); //a2 is addr of phys_mem_access_prot function
|
|
|
|
/// Char we show before each debug print
|
|
const char program_name[] = "chipsec";
|
|
|
|
// list of allocated memory
|
|
struct allocated_mem_list allocated_mem_list;
|
|
|
|
typedef struct tagCONTEXT {
|
|
unsigned long a; // rax - 0x00; eax - 0x0
|
|
unsigned long b; // rbx - 0x08; ebx - 0x4
|
|
unsigned long c; // rcx - 0x10; ecx - 0x8
|
|
unsigned long d; // rdx - 0x18; edx - 0xc
|
|
} CONTEXT, *PCONTEXT;
|
|
typedef CONTEXT CPUID_CTX, *PCPUID_CTX;
|
|
|
|
void __cpuid__(CPUID_CTX * ctx);
|
|
|
|
typedef struct tagSMI_CONTEXT {
|
|
unsigned long c; // rcx - 0x00;
|
|
unsigned long d; // rdx - 0x08;
|
|
unsigned long r8; // r8 - 0x10;
|
|
unsigned long r9; // r9 - 0x18;
|
|
unsigned long r10; // r10 - 0x20;
|
|
unsigned long r11; // r11 - 0x28;
|
|
unsigned long r12; // r12 - 0x30;
|
|
} SMI_CONTEXT, *SMI_PCONTEXT;
|
|
|
|
typedef SMI_CONTEXT SMI_CTX, *PSMI_CTX;
|
|
|
|
void __swsmi__(SMI_CTX * ctx);
|
|
|
|
void _rdmsr(
|
|
unsigned long msr_num, // rdi
|
|
unsigned long * msr_lo, // rsi
|
|
unsigned long * msr_hi // rdx
|
|
);
|
|
|
|
void _wrmsr(
|
|
unsigned long msr_num, // rdi
|
|
unsigned long msr_hi, // rsi
|
|
unsigned long msr_lo // rdx
|
|
);
|
|
|
|
unsigned int
|
|
ReadPortDword (
|
|
unsigned short port_num // rdi
|
|
);
|
|
|
|
unsigned short
|
|
ReadPortWord (
|
|
unsigned short port_num // rdi
|
|
);
|
|
|
|
unsigned char
|
|
ReadPortByte (
|
|
unsigned short port_num // rdi
|
|
);
|
|
|
|
void
|
|
WritePortByte (
|
|
unsigned char out_value, // rdi
|
|
unsigned short port_num // rsi
|
|
);
|
|
|
|
void
|
|
WritePortWord (
|
|
unsigned short out_value, // rdi
|
|
unsigned short port_num // rsi
|
|
);
|
|
|
|
void
|
|
WritePortDword (
|
|
unsigned int out_value, // rdi
|
|
unsigned short port_num // rsi
|
|
);
|
|
|
|
void
|
|
WritePCIByte (
|
|
unsigned int pci_reg, // rdi
|
|
unsigned short cfg_data_port, // rsi
|
|
unsigned char byte_value // rdx
|
|
);
|
|
|
|
void
|
|
WritePCIWord (
|
|
unsigned int pci_reg, // rdi
|
|
unsigned short cfg_data_port, // rsi
|
|
unsigned short word_value // rdx
|
|
);
|
|
|
|
void
|
|
WritePCIDword (
|
|
unsigned int pci_reg, // rdi
|
|
unsigned short cfg_data_port, // rsi
|
|
unsigned int dword_value // rdx
|
|
);
|
|
|
|
unsigned char
|
|
ReadPCIByte (
|
|
unsigned int pci_reg, // rdi
|
|
unsigned short cfg_data_port // rsi
|
|
);
|
|
|
|
unsigned short
|
|
ReadPCIWord (
|
|
unsigned int pci_reg, // rdi
|
|
unsigned short cfg_data_port // rsi
|
|
);
|
|
|
|
unsigned int
|
|
ReadPCIDword (
|
|
unsigned int pci_reg, // rdi
|
|
unsigned short cfg_data_port // rsi
|
|
);
|
|
|
|
unsigned long ReadCR0(void);
|
|
unsigned long ReadCR2(void);
|
|
unsigned long ReadCR3(void);
|
|
unsigned long ReadCR4(void);
|
|
#ifdef __x86_64__
|
|
unsigned long ReadCR8(void);
|
|
#endif
|
|
|
|
void WriteCR0( unsigned long );
|
|
void WriteCR2( unsigned long );
|
|
void WriteCR3( unsigned long );
|
|
void WriteCR4( unsigned long );
|
|
#ifdef __x86_64__
|
|
void WriteCR8( unsigned long );
|
|
#endif
|
|
|
|
unsigned long
|
|
hypercall(
|
|
unsigned long rcx_val,
|
|
unsigned long rdx_val,
|
|
unsigned long r8_val,
|
|
unsigned long r9_val,
|
|
unsigned long r10_val,
|
|
unsigned long r11_val,
|
|
unsigned long rax_val,
|
|
unsigned long rbx_val,
|
|
unsigned long rdi_val,
|
|
unsigned long rsi_val,
|
|
unsigned long xmm_buffer,
|
|
unsigned long hypercall_page
|
|
);
|
|
|
|
unsigned long hypercall_page(void);
|
|
|
|
void __cpuid__(CPUID_CTX * ctx);
|
|
|
|
void _store_idtr(
|
|
uint16_t *address // rdi
|
|
);
|
|
|
|
void _store_gdtr(
|
|
uint16_t *address // rdi
|
|
);
|
|
|
|
void _store_ldtr(
|
|
uint16_t *address // rdi
|
|
);
|
|
|
|
uint32_t
|
|
ReadPCICfg(
|
|
uint8_t bus,
|
|
uint8_t dev,
|
|
uint8_t fun,
|
|
uint8_t off,
|
|
uint8_t len // 1, 2, 4 bytes
|
|
)
|
|
{
|
|
unsigned int result = 0;
|
|
unsigned int pci_addr = (0x80000000 | (bus << 16) | (dev << 11) | (fun << 8) | (off & ~3));
|
|
unsigned short cfg_data_port = (uint16_t)(0xCFC + ( off & 0x3 ));
|
|
if ( 1 == len ) result = (ReadPCIByte ( pci_addr, cfg_data_port ) & 0xFF);
|
|
else if( 2 == len ) result = (ReadPCIWord ( pci_addr, cfg_data_port ) & 0xFFFF);
|
|
else if( 4 == len ) result = ReadPCIDword( pci_addr, cfg_data_port );
|
|
return result;
|
|
}
|
|
|
|
void
|
|
WritePCICfg(
|
|
uint8_t bus,
|
|
uint8_t dev,
|
|
uint8_t fun,
|
|
uint8_t off,
|
|
uint8_t len, // 1, 2, 4 bytes
|
|
uint32_t val
|
|
)
|
|
{
|
|
uint32_t pci_addr = (0x80000000 | (bus << 16) | (dev << 11) | (fun << 8) | (off & ~3));
|
|
uint16_t cfg_data_port = (uint16_t)(0xCFC + ( off & 0x3 ));
|
|
if ( 1 == len ) WritePCIByte ( pci_addr, cfg_data_port, (uint8_t)(val&0xFF) );
|
|
else if( 2 == len ) WritePCIWord ( pci_addr, cfg_data_port, (uint16_t)(val&0xFFFF) );
|
|
else if( 4 == len ) WritePCIDword( pci_addr, cfg_data_port, val );
|
|
}
|
|
|
|
void
|
|
WriteIOPort(
|
|
uint32_t value,
|
|
uint16_t io_port,
|
|
uint8_t len // 1, 2, 4 bytes
|
|
)
|
|
{
|
|
if ( 1 == len ) WritePortByte ( (uint8_t)(value&0xFF), io_port );
|
|
else if( 2 == len ) WritePortWord ( (uint16_t)(value&0xFFFF), io_port );
|
|
else if( 4 == len ) WritePortDword( value, io_port );
|
|
}
|
|
|
|
uint32_t
|
|
ReadIOPort(
|
|
uint16_t io_port,
|
|
uint8_t len // 1, 2, 4 bytes
|
|
)
|
|
{
|
|
if ( 1 == len ) return (ReadPortByte( io_port ) & 0xFF);
|
|
else if( 2 == len ) return (ReadPortWord( io_port ) & 0xFFFF);
|
|
else if( 4 == len ) return ReadPortDword( io_port );
|
|
return 0;
|
|
}
|
|
|
|
/* Own implementation of xlate_dev_mem_ptr
|
|
* (so we can read highmem and other)
|
|
*
|
|
* Input: physical address
|
|
* Output: pointer to virtual address where requested
|
|
* physical address is mapped
|
|
*/
|
|
|
|
static void *my_xlate_dev_mem_ptr(unsigned long phys)
|
|
{
|
|
|
|
void *addr=NULL;
|
|
unsigned long start = phys & PAGE_MASK;
|
|
unsigned long pfn = PFN_DOWN(phys);
|
|
|
|
/* If page is RAM, we can use __va. Otherwise ioremap and unmap. */
|
|
if (chipsec_page_is_ram(start >> PAGE_SHIFT)) {
|
|
if (PageHighMem(pfn_to_page(pfn))) {
|
|
/* The buffer does not have a mapping. Map it! */
|
|
addr = kmap(pfn_to_page(pfn));
|
|
return addr;
|
|
}
|
|
return __va(phys);
|
|
}
|
|
|
|
// Not RAM, so it is some device (can be bios for example)
|
|
addr = (void __force *)IOREMAP_NO_CACHE(start, PAGE_SIZE);
|
|
|
|
if (!addr)
|
|
addr = (void __force *)ioremap_prot(start, PAGE_SIZE,0);
|
|
|
|
if (addr)
|
|
addr = (void *)((unsigned long)addr | (phys & ~PAGE_MASK));
|
|
|
|
return addr;
|
|
}
|
|
|
|
// Our own implementation of unxlate_dev_mem_ptr
|
|
// (so we can read highmem and other)
|
|
static void my_unxlate_dev_mem_ptr(unsigned long phys,void *addr)
|
|
{
|
|
unsigned long pfn = PFN_DOWN(phys); //get page number
|
|
|
|
/* If page is RAM, check for highmem, and eventualy do nothing.
|
|
Otherwise need to iounmap. */
|
|
if (chipsec_page_is_ram((phys >> PAGE_SHIFT))) {
|
|
if (PageHighMem(pfn_to_page(pfn))) {
|
|
/* Need to kunmap kmaped memory*/
|
|
kunmap(pfn_to_page(pfn));
|
|
dbgprint ("unxlate: Highmem detected");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Not RAM, so it is some device (can be bios for example)
|
|
iounmap((void __iomem *)((unsigned long)addr & PAGE_MASK));
|
|
}
|
|
|
|
/*-- original (stripped) linux/drivers/char/mem.c starts here ---
|
|
only one mem device (fmem) was left
|
|
only read operation is supported
|
|
some not necessary pieces may survived, feel free to clean them
|
|
--------------------------------------------------------------*/
|
|
|
|
/*
|
|
* Architectures vary in how they handle caching for addresses
|
|
* outside of main memory.
|
|
*
|
|
*/
|
|
static inline int uncached_access(struct file *file, unsigned long addr)
|
|
{
|
|
#if defined(CONFIG_IA64)
|
|
/*
|
|
* On ia64, we ignore O_SYNC because we cannot tolerate memory attribute aliases.
|
|
*/
|
|
return !(efi_mem_attributes(addr) & EFI_MEMORY_WB);
|
|
#elif defined(CONFIG_MIPS)
|
|
{
|
|
extern int __uncached_access(struct file *file, unsigned long addr);
|
|
|
|
return __uncached_access(file, addr);
|
|
}
|
|
#else
|
|
/*
|
|
* Accessing memory above the top the kernel knows about or through a file pointer
|
|
* that was marked O_SYNC will be done non-cached.
|
|
*/
|
|
if (file->f_flags & O_SYNC)
|
|
return 1;
|
|
return addr >= __pa(high_memory);
|
|
#endif
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,8,0)
|
|
/* copy_from_kernel_nofault and copy_to_kernel_nofault were introduced in Linux
|
|
* 5.8.0. Before, they were called probe_kernel_read and probe_kernel_write
|
|
* (cf. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=fe557319aa06c23cffc9346000f119547e0f289a).
|
|
*
|
|
* As copy_to_kernel_nofault symbol is not exported, do not use it.
|
|
*/
|
|
long copy_from_kernel_nofault(void *dst, const void *src, size_t size)
|
|
{
|
|
return probe_kernel_read(dst, src, size);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* This function reads the *physical* memory. The f_pos points directly to the
|
|
* memory location.
|
|
*/
|
|
static ssize_t read_mem(struct file * file, char __user * buf, size_t count, loff_t *ppos)
|
|
{
|
|
unsigned long p = *ppos;
|
|
ssize_t read = 0;
|
|
size_t sz;
|
|
void *ptr, *bounce;
|
|
int err;
|
|
|
|
#ifdef __ARCH_HAS_NO_PAGE_ZERO_MAPPED
|
|
/* we don't have page 0 mapped on sparc and m68k.. */
|
|
if (p < PAGE_SIZE) {
|
|
sz = PAGE_SIZE - p;
|
|
if (sz > count)
|
|
sz = count;
|
|
if (sz > 0) {
|
|
if (clear_user(buf, sz))
|
|
return -EFAULT;
|
|
buf += sz;
|
|
p += sz;
|
|
count -= sz;
|
|
read += sz;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Allocate a bounce buffer to chain copy_from_kernel_nofault with copy_to_user */
|
|
bounce = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
|
if (!bounce) {
|
|
printk(KERN_ALERT "[chipsec] ERROR: STATUS_UNSUCCESSFUL - could not allocate memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
while (count > 0) {
|
|
/*
|
|
* Handle first page in case it's not aligned
|
|
*/
|
|
if (-p & (PAGE_SIZE - 1))
|
|
sz = -p & (PAGE_SIZE - 1);
|
|
else
|
|
sz = PAGE_SIZE;
|
|
|
|
if (sz > count)
|
|
sz = count;
|
|
|
|
/*
|
|
* On ia64 if a page has been mapped somewhere as
|
|
* uncached, then it must also be accessed uncached
|
|
* by the kernel or data corruption may occur
|
|
*/
|
|
ptr = my_xlate_dev_mem_ptr(p);
|
|
if (!ptr){
|
|
dbgprint("xlate FAIL, p: %lX", p);
|
|
kfree(bounce);
|
|
return -EFAULT;
|
|
}
|
|
|
|
memset(bounce, 0, sz);
|
|
err = copy_from_kernel_nofault(bounce, ptr, sz);
|
|
if (err) {
|
|
dbgprint("copy_from_kernel_nofault FAIL %d, ptr: %lX / %lx",
|
|
err, p, (unsigned long)ptr);
|
|
my_unxlate_dev_mem_ptr(p, ptr);
|
|
kfree(bounce);
|
|
return -EFAULT;
|
|
}
|
|
err = copy_to_user(buf, bounce, sz);
|
|
if (err) {
|
|
dbgprint("copy_to_user FAIL %d, ptr: %lX / %lx",
|
|
err, p, (unsigned long)ptr);
|
|
my_unxlate_dev_mem_ptr(p, ptr);
|
|
kfree(bounce);
|
|
return -EFAULT;
|
|
}
|
|
|
|
my_unxlate_dev_mem_ptr(p, ptr);
|
|
|
|
buf += sz;
|
|
p += sz;
|
|
count -= sz;
|
|
read += sz;
|
|
}
|
|
*ppos += read;
|
|
kfree(bounce);
|
|
return read;
|
|
}
|
|
|
|
static ssize_t write_mem(struct file * file, const char __user * buf, size_t count, loff_t *ppos)
|
|
{
|
|
unsigned long p = *ppos;
|
|
ssize_t written = 0;
|
|
size_t sz;
|
|
void *ptr;
|
|
|
|
#ifdef __ARCH_HAS_NO_PAGE_ZERO_MAPPED
|
|
/* we don't have page 0 mapped on sparc and m68k.. */
|
|
if (p < PAGE_SIZE) {
|
|
sz = PAGE_SIZE - p;
|
|
if (sz > count)
|
|
sz = count;
|
|
if (sz > 0) {
|
|
buf += sz;
|
|
p += sz;
|
|
count -= sz;
|
|
written += sz;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
while (count > 0) {
|
|
/*
|
|
* Handle first page in case it's not aligned
|
|
*/
|
|
if (-p & (PAGE_SIZE - 1))
|
|
sz = -p & (PAGE_SIZE - 1);
|
|
else
|
|
sz = PAGE_SIZE;
|
|
|
|
if (sz > count)
|
|
sz = count;
|
|
|
|
/*
|
|
* On ia64 if a page has been mapped somewhere as
|
|
* uncached, then it must also be accessed uncached
|
|
* by the kernel or data corruption may occur
|
|
*/
|
|
ptr = my_xlate_dev_mem_ptr(p);
|
|
if (!ptr) {
|
|
dbgprint("xlate FAIL, p: %lX", p);
|
|
return -EFAULT;
|
|
}
|
|
|
|
/*
|
|
* It would be safer to use probe_kernel_write() or
|
|
* copy_to_kernel_nofault() with a bounce buffer instead of
|
|
* directly copying user data to the destination. But these
|
|
* functions are no longer exported since Linux 5.8.0
|
|
* (cf. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0493cb086353e786be56010780a0b7025b5db34c)
|
|
*/
|
|
if (copy_from_user(ptr, buf, sz)) {
|
|
dbgprint("copy_from_user FAIL, ptr: %lX / %lx",
|
|
p, (unsigned long)ptr);
|
|
my_unxlate_dev_mem_ptr(p, ptr);
|
|
return -EFAULT;
|
|
}
|
|
|
|
my_unxlate_dev_mem_ptr(p, ptr);
|
|
|
|
buf += sz;
|
|
p += sz;
|
|
count -= sz;
|
|
written += sz;
|
|
}
|
|
|
|
*ppos += written;
|
|
return written;
|
|
}
|
|
|
|
#ifndef CONFIG_MMU
|
|
static unsigned long get_unmapped_area_mem(struct file *file,
|
|
unsigned long addr,
|
|
unsigned long len,
|
|
unsigned long pgoff,
|
|
unsigned long flags)
|
|
{
|
|
if (!valid_mmap_phys_addr_range(pgoff, len))
|
|
return (unsigned long) -EINVAL;
|
|
return pgoff << PAGE_SHIFT;
|
|
}
|
|
|
|
/* can't do an in-place private mapping if there's no MMU */
|
|
static inline int private_mapping_ok(struct vm_area_struct *vma)
|
|
{
|
|
return vma->vm_flags & VM_MAYSHARE;
|
|
}
|
|
#else
|
|
#define get_unmapped_area_mem NULL
|
|
|
|
static inline int private_mapping_ok(struct vm_area_struct *vma)
|
|
{
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
int __weak phys_mem_access_prot_allowed(struct file *file,
|
|
unsigned long pfn, unsigned long size, pgprot_t *vma_prot)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
#if defined(__HAVE_ARCH_PAX_OPEN_USERLAND)
|
|
#if LINUX_VERSION_CODE > KERNEL_VERSION(4,14,0) && defined(ARCH_HAS_VALID_PHYS_ADDR_RANGE)
|
|
int valid_mmap_phys_addr_range(unsigned long pfn, size_t size)
|
|
{
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
#else
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,12) && defined(ARCH_HAS_VALID_PHYS_ADDR_RANGE)
|
|
int valid_mmap_phys_addr_range(unsigned long pfn, size_t size)
|
|
{
|
|
return 1;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef ARCH_HAS_VALID_PHYS_ADDR_RANGE
|
|
static inline int valid_phys_addr_range(phys_addr_t addr, size_t count)
|
|
{
|
|
return addr + count <= __pa(high_memory);
|
|
}
|
|
|
|
static inline int valid_mmap_phys_addr_range(unsigned long pfn, size_t size)
|
|
{
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
#ifndef __HAVE_PHYS_MEM_ACCESS_PROT
|
|
static pgprot_t cs_phys_mem_access_prot(struct file *file, unsigned long pfn,
|
|
unsigned long size, pgprot_t vma_prot)
|
|
{
|
|
#ifdef pgprot_noncached
|
|
phys_addr_t offset = pfn << PAGE_SHIFT;
|
|
|
|
if (uncached_access(file, offset))
|
|
return pgprot_noncached(vma_prot);
|
|
#endif
|
|
return vma_prot;
|
|
}
|
|
#endif
|
|
|
|
static phys_addr_t virt_2_phys(void *vaddr)
|
|
{
|
|
#if defined(CONFIG_X86) && LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,0)
|
|
if (!virt_addr_valid(vaddr))
|
|
return slow_virt_to_phys(vaddr);
|
|
#endif
|
|
return virt_to_phys(vaddr);
|
|
}
|
|
|
|
static const struct vm_operations_struct mmap_mem_ops = {
|
|
#ifdef CONFIG_HAVE_IOREMAP_PROT
|
|
.access = generic_access_phys
|
|
#endif
|
|
};
|
|
|
|
static int mmap_mem(struct file * file, struct vm_area_struct * vma)
|
|
{
|
|
size_t size = vma->vm_end - vma->vm_start;
|
|
|
|
if (!valid_mmap_phys_addr_range(vma->vm_pgoff, size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!private_mapping_ok(vma)) {
|
|
return -ENOSYS;
|
|
}
|
|
|
|
if (!phys_mem_access_prot_allowed(file, vma->vm_pgoff, size,
|
|
&vma->vm_page_prot)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
// We skip devmem_is_allowed / range_is_allowed checking here
|
|
// because we want to be able to mmap MMIO regions
|
|
|
|
vma->vm_page_prot = guess_phys_mem_access_prot(file, vma->vm_pgoff,
|
|
size,
|
|
vma->vm_page_prot);
|
|
|
|
vma->vm_ops = &mmap_mem_ops;
|
|
|
|
if (remap_pfn_range(vma,
|
|
vma->vm_start,
|
|
vma->vm_pgoff,
|
|
size,
|
|
vma->vm_page_prot)) {
|
|
return -EAGAIN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The memory devices use the full 32/64 bits of the offset, and so we cannot
|
|
* check against negative addresses: they are ok. The return value is weird,
|
|
* though, in that case (0).
|
|
*
|
|
* also note that seeking relative to the "end of file" isn't supported:
|
|
* it has no meaning, so it returns -EINVAL.
|
|
*/
|
|
static loff_t memory_lseek(struct file * file, loff_t offset, int orig)
|
|
{
|
|
loff_t ret;
|
|
//Older kernels (<20) uses f_dentry instead of f_path.dentry
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
|
|
mutex_lock(&file->f_dentry->d_inode->i_mutex);
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
|
|
inode_lock(file->f_path.dentry->d_inode);
|
|
#else
|
|
mutex_lock(&file->f_path.dentry->d_inode->i_mutex);
|
|
#endif
|
|
|
|
switch (orig) {
|
|
case 0:
|
|
file->f_pos = offset;
|
|
ret = file->f_pos;
|
|
force_successful_syscall_return();
|
|
break;
|
|
case 1:
|
|
file->f_pos += offset;
|
|
ret = file->f_pos;
|
|
force_successful_syscall_return();
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
//Older kernels (<20) uses f_dentry instead of f_path.dentry
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
|
|
mutex_unlock(&file->f_dentry->d_inode->i_mutex);
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
|
|
inode_unlock(file->f_path.dentry->d_inode);
|
|
#else
|
|
mutex_unlock(&file->f_path.dentry->d_inode->i_mutex);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef EFI_NOT_READY
|
|
void print_stat(efi_status_t stat)
|
|
{
|
|
switch (stat) {
|
|
case EFI_SUCCESS:
|
|
printk( KERN_DEBUG "EFI_SUCCESS\n");
|
|
break;
|
|
case EFI_LOAD_ERROR:
|
|
printk( KERN_DEBUG "EFI_LOAD_ERROR\n");
|
|
break;
|
|
case EFI_INVALID_PARAMETER:
|
|
printk( KERN_DEBUG "EFI_INVALID_PARAMETER\n");
|
|
break;
|
|
case EFI_UNSUPPORTED:
|
|
printk( KERN_DEBUG "EFI_UNSUPPORTED\n");
|
|
break;
|
|
case EFI_BAD_BUFFER_SIZE:
|
|
printk( KERN_DEBUG "EFI_BAD_BUFFER_SIZE\n");
|
|
break;
|
|
case EFI_BUFFER_TOO_SMALL:
|
|
printk( KERN_DEBUG "EFI_BUFFER_TOO_SMALL\n");
|
|
break;
|
|
case EFI_NOT_READY:
|
|
printk( KERN_DEBUG "EFI_NOT_READY\n");
|
|
break;
|
|
case EFI_DEVICE_ERROR:
|
|
printk( KERN_DEBUG "EFI_DEVICE_ERROR\n");
|
|
break;
|
|
case EFI_WRITE_PROTECTED:
|
|
printk( KERN_DEBUG "EFI_WRITE_PROTECTED\n");
|
|
break;
|
|
case EFI_OUT_OF_RESOURCES:
|
|
printk( KERN_DEBUG "EFI_OUT_OF_RESOURCES\n");
|
|
break;
|
|
case EFI_NOT_FOUND:
|
|
printk( KERN_DEBUG "EFI_NOT_FOUND\n");
|
|
break;
|
|
case EFI_SECURITY_VIOLATION:
|
|
printk( KERN_DEBUG "EFI_SECURITY_VIOLATION\n");
|
|
break;
|
|
default:
|
|
printk( KERN_DEBUG "Unknown status\n");
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void apply_ucode_patch(void *info)
|
|
{
|
|
CPUID_CTX *cpuinfo = (CPUID_CTX *)info;
|
|
__cpuid__(cpuinfo);
|
|
}
|
|
|
|
static unsigned long hypercall_page_c(void)
|
|
{
|
|
return hypercall_page();
|
|
}
|
|
|
|
static long d_ioctl(struct file *file, unsigned int ioctl_num, unsigned long ioctl_param)
|
|
{
|
|
int numargs = 0;
|
|
unsigned long ptrbuf[16];
|
|
unsigned long *ptr = ptrbuf;
|
|
unsigned short ucode_size;
|
|
unsigned short thread_id;
|
|
char *ucode_buf;
|
|
//unsigned int counter;
|
|
char small_buffer[6]; //32 bits + char + \0
|
|
unsigned long CPUInfo[4]={1,0,0,0};
|
|
|
|
switch(ioctl_num)
|
|
{
|
|
case IOCTL_BASE:
|
|
{
|
|
return ((IOCTL_RDIO & 0xfffffff0) >> 4);
|
|
}
|
|
case IOCTL_RDIO:
|
|
{
|
|
//IN params: addr, size
|
|
//OUT params: val
|
|
#ifdef CONFIG_X86
|
|
numargs = 3;
|
|
if( copy_from_user( (void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs) ) > 0)
|
|
return -EFAULT;
|
|
|
|
switch( ptr[1] )
|
|
{
|
|
case 1:
|
|
ptr[2] = ReadPortByte(ptr[0]);
|
|
// BUGFIX: eax is returning more than a byte of data ??
|
|
ptr[2] = ptr[2] & 0xff;
|
|
break;
|
|
case 2:
|
|
ptr[2] = ReadPortWord(ptr[0]);
|
|
break;
|
|
default: // 4
|
|
ptr[2] = ReadPortDword(ptr[0]);
|
|
break;
|
|
}
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
|
|
case IOCTL_WRIO:
|
|
{
|
|
//IN params: addr, size, val
|
|
#ifdef CONFIG_X86
|
|
|
|
numargs = 3;
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
|
|
switch(ptr[1])
|
|
{
|
|
case 1:
|
|
WritePortByte(ptr[2],ptr[0]);
|
|
break;
|
|
case 2:
|
|
WritePortWord(ptr[2],ptr[0]);
|
|
break;
|
|
default:
|
|
WritePortDword(ptr[2],ptr[0]);
|
|
break;
|
|
}
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
|
|
case IOCTL_LOAD_UCODE_PATCH:
|
|
{
|
|
|
|
#ifdef CONFIG_X86
|
|
unsigned long long ucode_start = 0;
|
|
u32 _eax[2] = {0}, _edx[2] = {0};
|
|
ucode_size=0;
|
|
|
|
printk(KERN_INFO "[chipsec][IOCTL_LOAD_UCODE_UPDATE] Initializing update routine\n");
|
|
|
|
/* first byte: thread_id */
|
|
memset(small_buffer, 0x00, 6);
|
|
|
|
if ( copy_from_user(&small_buffer, (char *) ioctl_param, sizeof(unsigned char)) > 0 )
|
|
return -EFAULT;
|
|
|
|
thread_id=(unsigned short) small_buffer[0];
|
|
|
|
memset(small_buffer, 0x00, 6);
|
|
|
|
if ( copy_from_user(&small_buffer, (unsigned char *)(ioctl_param+sizeof(unsigned char)), sizeof(unsigned short)) > 0 )
|
|
return -EFAULT;
|
|
|
|
ucode_size=(unsigned short) *((unsigned short *) small_buffer);
|
|
|
|
ucode_buf=kmalloc(ucode_size, GFP_KERNEL);
|
|
if (!ucode_buf)
|
|
return -EFAULT;
|
|
|
|
ucode_start = (unsigned long long) ucode_start;
|
|
memset(ucode_buf, 0, ucode_size);
|
|
|
|
if ( copy_from_user(ucode_buf, (unsigned char *)(ioctl_param+sizeof(unsigned char)+sizeof(unsigned short)), ucode_size) > 0 )
|
|
return -EFAULT;
|
|
|
|
printk(KERN_INFO "[chipsec] [patch_apply_ucode] Checking current patch ID");
|
|
rdmsr_on_cpu(thread_id, MSR_IA32_BIOS_SIGN_ID, (u32*)&_eax[0], (u32*)&_edx[0]);
|
|
|
|
printk(KERN_INFO "[chipsec] [patch_apply_ucode] Applying patch in the processor id: %d", thread_id);
|
|
wrmsr_on_cpu(thread_id, MSR_IA32_BIOS_UPDT_TRIG, (u32)ucode_start, (u32)((ucode_start >> 32) & 0xffffffff));
|
|
|
|
kfree(ucode_buf);
|
|
|
|
printk(KERN_INFO "[chipsec] [patch_apply_ucode] checking ucode update was loaded..\n");
|
|
printk(KERN_INFO "[chipsec] [patch_apply_ucode] clear IA32_BIOS_SIGN_ID, CPUID EAX=1, read back IA32_BIOS_SIGN_ID\n" );
|
|
|
|
wrmsr_on_cpu(thread_id, MSR_IA32_BIOS_SIGN_ID, (u32)_eax[1], (u32)_edx[1]);
|
|
smp_call_function_single(thread_id, apply_ucode_patch, (void *)CPUInfo,0);
|
|
rdmsr_on_cpu(thread_id, MSR_IA32_BIOS_SIGN_ID, (u32*)&_eax[1], (u32*)&_edx[1]);
|
|
|
|
if (_edx[1] != _edx[0])
|
|
printk(KERN_INFO "[chipsec][IOCTL_LOAD_UCODE_UPDATE] Microcode update loaded (ID != %u)\n", _edx[0]);
|
|
else
|
|
printk(KERN_INFO "[chipsec][IOCTL_LOAD_UCODE_UPDATE] Microcode update failed");
|
|
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
|
|
case IOCTL_RDMSR:
|
|
{
|
|
//IN params: threadid, msr_addr
|
|
//OUT params: edx, eax
|
|
#ifdef CONFIG_X86
|
|
|
|
numargs = 4;
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
|
|
rdmsr_on_cpu(ptr[0], ptr[1], (u32*)&ptr[3], (u32*)&ptr[2]);
|
|
//_rdmsr(ptr[1],&ptr[3],&ptr[2]);
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
|
|
case IOCTL_WRMSR:
|
|
{
|
|
//IN params: threadid, msr_addr, {e,r}dx, {e,r}ax
|
|
#ifdef CONFIG_X86
|
|
numargs = 4;
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
|
|
wrmsr_on_cpu(ptr[0], ptr[1], (u32)ptr[3], (u32)ptr[2]);
|
|
//_wrmsr(ptr[1],ptr[3],ptr[2]);
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
|
|
case IOCTL_CPUID:
|
|
{
|
|
//IN params: {e,r}ax
|
|
//OUT params: {e,r}ax, {e,r}bx, {e,r}cx, {e,r}dx
|
|
#ifdef CONFIG_X86
|
|
numargs = 4;
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
|
|
__cpuid__((CPUID_CTX *)ptr);
|
|
|
|
if(copy_to_user( (void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
|
|
}
|
|
|
|
case IOCTL_RDPCI:
|
|
{
|
|
//IN params: bus, dev, fun, off, len
|
|
//OUT params: value
|
|
#ifdef CONFIG_PCI
|
|
|
|
uint32_t bus, dev, fun, off, len, val;
|
|
numargs = 5;
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
|
|
bus = ptr[0] & 0xffff;
|
|
dev = ptr[1] >> 16 & 0xffff;
|
|
fun = ptr[1] & 0xffff;
|
|
off = ptr[2];
|
|
len = ptr[3];
|
|
|
|
val = ReadPCICfg( bus, dev, fun, off, len );
|
|
|
|
ptr[4] = val;
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
|
|
case IOCTL_WRPCI:
|
|
{
|
|
//IN params: bus, dev, fun, off, len, val
|
|
#ifdef CONFIG_PCI
|
|
|
|
uint32_t bus, dev, fun, off, len, val;
|
|
numargs = 5;
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
|
|
bus = ptr[0] & 0xffff;
|
|
dev = ptr[1] >> 16 & 0xffff;
|
|
fun = ptr[1] & 0xffff;
|
|
off = ptr[2];
|
|
val = ptr[4];
|
|
len = ptr[3];
|
|
|
|
WritePCICfg( bus, dev, fun, off, len, val );
|
|
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
|
|
case IOCTL_GET_CPU_DESCRIPTOR_TABLE:
|
|
{
|
|
//IN params: cpu_thread_id, desc_table_code, 0, 0, 0
|
|
//OUT params: limit, base_hi, base_lo, phys_hi, phys_lo
|
|
#ifdef CONFIG_X86
|
|
DESCRIPTOR_TABLE_RECORD dtr;
|
|
PDESCRIPTOR_TABLE_RECORD pdtr = &dtr;
|
|
PHYSICAL_ADDRESS dt_pa;
|
|
|
|
numargs = 5;
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
|
|
switch(ptr[1])
|
|
{
|
|
case CPU_DT_CODE_GDTR:
|
|
{
|
|
_store_gdtr(&pdtr->limit);
|
|
break;
|
|
}
|
|
case CPU_DT_CODE_LDTR:
|
|
{
|
|
_store_ldtr(&pdtr->limit);
|
|
break;
|
|
}
|
|
case CPU_DT_CODE_IDTR:
|
|
{
|
|
_store_idtr(&pdtr->limit);
|
|
break;
|
|
}
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
dt_pa.quadpart = virt_2_phys((void*)dtr.base);
|
|
ptr[0] = dtr.limit;
|
|
#ifdef __x86_64__
|
|
ptr[1] = (uint32_t)(dtr.base >> 32);
|
|
#else
|
|
ptr[1] = 0;
|
|
#endif
|
|
ptr[2] = (uint32_t)dtr.base;
|
|
|
|
//#pragma GCC diagnostic ignored "-Wuninitialized" dt_pa.u.high
|
|
//#pragma GCC diagnostic ignored "-Wuninitialized" dt_pa.u.low
|
|
ptr[3] = dt_pa.u.high;
|
|
ptr[4] = dt_pa.u.low;
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
|
|
case IOCTL_SWSMI:
|
|
{
|
|
//IN params: SMI_code_data, _rax, _rbx, _rcx, _rdx, _rsi, _rdi
|
|
#ifdef CONFIG_X86
|
|
|
|
printk( KERN_INFO "[chipsec] > IOCTL_SWSMI\n");
|
|
numargs = 7;
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs)) > 0)
|
|
{
|
|
printk( KERN_ALERT "[chipsec] ERROR: STATUS_INVALID_PARAMETER\n" );
|
|
break;
|
|
}
|
|
|
|
__swsmi__((SMI_CTX *)ptr);
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
|
|
case IOCTL_RDCR:
|
|
{
|
|
//IN params: number of CR reg
|
|
//OUT params: val
|
|
#ifdef CONFIG_X86
|
|
numargs = 3;
|
|
if( copy_from_user( (void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs) ) > 0)
|
|
return -EFAULT;
|
|
|
|
switch( ptr[1] )
|
|
{
|
|
case 0:
|
|
ptr[2] = ReadCR0();
|
|
break;
|
|
case 2:
|
|
ptr[2] = ReadCR2();
|
|
break;
|
|
case 3:
|
|
ptr[2] = ReadCR3();
|
|
break;
|
|
case 4:
|
|
ptr[2] = ReadCR4();
|
|
break;
|
|
case 8:
|
|
#ifdef __x86_64__
|
|
ptr[2] = ReadCR8();
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
case IOCTL_WRCR:
|
|
{
|
|
//IN params: number of CR reg
|
|
//OUT params: val
|
|
#ifdef CONFIG_X86
|
|
numargs = 3;
|
|
if( copy_from_user( (void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs) ) > 0)
|
|
return -EFAULT;
|
|
|
|
switch( ptr[1] )
|
|
{
|
|
case 0:
|
|
WriteCR0(ptr[2]);
|
|
break;
|
|
case 2:
|
|
WriteCR2(ptr[2]);
|
|
break;
|
|
case 3:
|
|
WriteCR3(ptr[2]);
|
|
break;
|
|
case 4:
|
|
WriteCR4(ptr[2]);
|
|
break;
|
|
case 8:
|
|
#ifdef __x86_64__
|
|
WriteCR8(ptr[2]);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
case IOCTL_ALLOC_PHYSMEM:
|
|
{
|
|
//IN params: size
|
|
//OUT params: physical address
|
|
uint32_t NumberOfBytes = 0;
|
|
void *va;
|
|
phys_addr_t pa, max_pa;
|
|
struct allocated_mem_list *tmp = NULL;
|
|
|
|
numargs = 2;
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs)) > 0)
|
|
{
|
|
printk( KERN_ALERT "[chipsec] ERROR: STATUS_INVALID_PARAMETER\n" );
|
|
return -EFAULT;
|
|
}
|
|
|
|
NumberOfBytes = ptr[0];
|
|
max_pa = ptr[1];
|
|
|
|
va = kmalloc(NumberOfBytes, GFP_KERNEL );
|
|
if( !va )
|
|
{
|
|
printk(KERN_ALERT "[chipsec] ERROR: STATUS_UNSUCCESSFUL - could not allocate memory\n" );
|
|
return -EFAULT;
|
|
}
|
|
|
|
memset(va, 0, NumberOfBytes);
|
|
pa = virt_to_phys(va);
|
|
|
|
if (pa > max_pa)
|
|
{
|
|
printk(KERN_ALERT "[chipsec] WARNING: allocated memory (0x%llx) is not below max_pa (0x%llx) (ignoring)", pa, max_pa);
|
|
}
|
|
|
|
tmp = kmalloc(sizeof(struct allocated_mem_list), GFP_KERNEL);
|
|
if (tmp == NULL) {
|
|
printk(KERN_ALERT "[chipsec] ERROR: STATUS_UNSUCCESSFUL - could not allocate memory\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
tmp->va = va;
|
|
tmp->pa = pa;
|
|
list_add(&(tmp->list), &(allocated_mem_list.list));
|
|
|
|
ptr[0] = (unsigned long)va;
|
|
ptr[1] = pa;
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
|
|
case IOCTL_FREE_PHYSMEM:
|
|
{
|
|
// IN params : physical address
|
|
// OUT params : 0 not freed, 1 freed
|
|
phys_addr_t pa = 0;
|
|
void *va = NULL;
|
|
struct list_head *pos, *q;
|
|
struct allocated_mem_list *element;
|
|
|
|
numargs = 1;
|
|
if (copy_from_user((void*) ptrbuf, (void*) ioctl_param, (sizeof(long) * numargs)) > 0) {
|
|
printk( KERN_ALERT "[chipsec] ERROR: STATUS_INVALID_PARAMETER\n" );
|
|
return -EFAULT;
|
|
}
|
|
|
|
pa = ptr[0];
|
|
|
|
// look for va inside allocated mem list
|
|
list_for_each_safe(pos, q, &allocated_mem_list.list) {
|
|
element = list_entry(pos, struct allocated_mem_list, list);
|
|
if (element->pa == pa) {
|
|
va = element->va;
|
|
list_del(pos);
|
|
kfree(element);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (va != NULL) {
|
|
// freeing
|
|
printk(KERN_INFO "[chipsec] freeing pa = 0x%llx, va = %p\n", pa, va);
|
|
kfree(va);
|
|
ptr[0] = 1;
|
|
} else {
|
|
ptr[0] = 0;
|
|
}
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0) {
|
|
return -EFAULT;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
#ifdef EFI_NOT_READY
|
|
case IOCTL_GET_EFIVAR:
|
|
{
|
|
//IN params: data_size, guid, namelen, name
|
|
//OUT params: var_size, stat, data
|
|
uint32_t *kbuf;
|
|
static efi_char16_t *name;
|
|
char *cptr, *var;
|
|
efi_guid_t GUID;
|
|
efi_status_t stat;
|
|
long unsigned int data_size, var_size;
|
|
uint32_t data_size_u32;
|
|
unsigned int namelen, index;
|
|
static struct efi *myefi;
|
|
myefi = &efi;
|
|
|
|
printk( KERN_INFO "[chipsec] > IOCTL_GET_EFIVAR\n");
|
|
|
|
// get the size (a uint32_t)
|
|
if(copy_from_user((void*)&data_size_u32, (void*)ioctl_param, sizeof(uint32_t)) > 0)
|
|
{
|
|
printk( KERN_ALERT "[chipsec] ERROR: STATUS_INVALID_PARAMETER\n" );
|
|
return -EFAULT;
|
|
}
|
|
data_size = (unsigned long)data_size_u32;
|
|
|
|
// check that this is enough memory
|
|
if (data_size < sizeof(uint32_t) * 13)
|
|
{
|
|
printk(KERN_ALERT "[chipsec] ERROR: INVALID SIZE PARAMETER\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
// allocate that much memory
|
|
kbuf = kzalloc(data_size, GFP_KERNEL);
|
|
if (!kbuf)
|
|
{
|
|
printk(KERN_ALERT "[chipsec] ERROR: STATUS_UNSUCCESSFUL - could not allocate memory\n" );
|
|
return -ENOMEM;
|
|
}
|
|
|
|
// fill kbuf with user's buffer
|
|
if(copy_from_user((void*)kbuf, (void*)ioctl_param, data_size) > 0)
|
|
{
|
|
kfree(kbuf);
|
|
printk( KERN_ALERT "[chipsec] ERROR: STATUS_INVALID_PARAMETER\n" );
|
|
return -EFAULT;
|
|
}
|
|
|
|
GUID = EFI_GUID( kbuf[1], kbuf[2], kbuf[3], kbuf[4], kbuf[5], kbuf[6], kbuf[7], kbuf[8], kbuf[9], kbuf[10], kbuf[11]);
|
|
|
|
namelen = kbuf[12];
|
|
cptr = (char *)&kbuf[13];
|
|
|
|
if (namelen > (data_size - sizeof(uint32_t) * 13))
|
|
{
|
|
kfree(kbuf);
|
|
printk(KERN_ALERT "[chipsec] ERROR: INVALID SIZE PARAMETER (namelen %u too big for data_size %lu)\n", namelen, data_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
// if name overflowed, we only work with the part that fit in kbuf
|
|
name = kzalloc((namelen+1)*sizeof(efi_char16_t), GFP_KERNEL);
|
|
if (!name)
|
|
{
|
|
kfree(kbuf);
|
|
printk(KERN_ALERT "[chipsec] ERROR: STATUS_UNSUCCESSFUL - could not allocate memory\n" );
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for(index=0; index < namelen; index++)
|
|
{
|
|
// upper byte is 0x0 (for ASCII), the lower byte is ASCII char
|
|
name[index] = (efi_char16_t)(cptr[index] & 0xFF);
|
|
}
|
|
name[index] = (efi_char16_t)(0);
|
|
|
|
// now we're done with everything in kbuf. use it for get_variable
|
|
// the format for kbuf is:
|
|
// size, return value, attrs, data
|
|
memset(kbuf, '\0', data_size);
|
|
|
|
// For the call to get_variable we reserve space for header information
|
|
var_size = data_size - (sizeof(uint32_t) * 3);
|
|
var = (char *)&kbuf[3];
|
|
stat = myefi->get_variable(name, &GUID, (u32*)&kbuf[2], &var_size, var);
|
|
|
|
if (stat != EFI_SUCCESS)
|
|
{
|
|
print_stat(stat);
|
|
data_size = sizeof(uint32_t) * 2;
|
|
}
|
|
else if (var_size > data_size - (sizeof(uint32_t) * 3))
|
|
{
|
|
printk(KERN_ALERT "[chipsec] ERROR: get_variable runtime service returned EFI_SUCCESS but the variable size was larger than the size passed. Possible corruption? (Should have returned \n" );
|
|
}
|
|
|
|
kbuf[0] = var_size;
|
|
kbuf[1] = stat;
|
|
// kbuf[2] = attributes from the get_variable call above
|
|
// kbuf[3..] = contents of the variable
|
|
if(copy_to_user((void*)ioctl_param, (void*)kbuf, data_size) > 0)
|
|
{
|
|
kfree(name);
|
|
kfree(kbuf);
|
|
return -EFAULT;
|
|
}
|
|
|
|
kfree(name);
|
|
kfree(kbuf);
|
|
|
|
break;
|
|
}
|
|
|
|
case IOCTL_SET_EFIVAR:
|
|
{
|
|
uint32_t *kbuf;
|
|
static efi_char16_t *name;
|
|
char *cptr, *data;
|
|
efi_guid_t GUID;
|
|
efi_status_t stat;
|
|
unsigned long data_size;
|
|
uint32_t data_size_u32, attr, datalen;
|
|
unsigned int namelen, index;
|
|
static struct efi *myefi;
|
|
myefi = &efi;
|
|
|
|
printk( KERN_INFO "[chipsec] > IOCTL_SET_EFIVAR\n");
|
|
|
|
//IN params: data_size, guid, attr, namelen, datalen, name, data
|
|
//OUT params: data_size, status
|
|
|
|
// get the size (a uint32_t)
|
|
if(copy_from_user((void*)&data_size_u32, (void*)ioctl_param, sizeof(uint32_t)) > 0)
|
|
{
|
|
printk( KERN_ALERT "[chipsec] ERROR: STATUS_INVALID_PARAMETER\n" );
|
|
return -EFAULT;
|
|
}
|
|
data_size = (unsigned long)data_size_u32;
|
|
|
|
// check that this is enough to store what we will expect
|
|
if (data_size < (sizeof(uint32_t) * 14))
|
|
{
|
|
printk(KERN_ALERT "[chipsec] ERROR: INVALID data_size PARAMETER\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
// allocate that much memory
|
|
kbuf = kzalloc(data_size, GFP_KERNEL);
|
|
if (!kbuf)
|
|
{
|
|
printk(KERN_ALERT "[chipsec] ERROR: STATUS_UNSUCCESSFUL - could not allocate memory\n" );
|
|
return -ENOMEM;
|
|
}
|
|
|
|
// fill kbuf with user's buffer
|
|
if(copy_from_user((void*)kbuf, (void*)ioctl_param, data_size) > 0)
|
|
{
|
|
printk( KERN_ALERT "[chipsec] ERROR: STATUS_INVALID_PARAMETER\n" );
|
|
kfree(kbuf);
|
|
return -EFAULT;
|
|
}
|
|
|
|
GUID = EFI_GUID( kbuf[1], kbuf[2], kbuf[3], kbuf[4], kbuf[5], kbuf[6], kbuf[7], kbuf[8], kbuf[9], kbuf[10], kbuf[11]);
|
|
|
|
attr = kbuf[12];
|
|
namelen = kbuf[13];
|
|
datalen = kbuf[14];
|
|
cptr = (char *)&kbuf[15];
|
|
|
|
// check for namelen underflow
|
|
if (data_size - (namelen) > data_size)
|
|
{
|
|
printk(KERN_ALERT "[chipsec] ERROR: INVALID name PARAMETER (namelen = %u)\n", namelen);
|
|
kfree(kbuf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
// make sure size that was passed in actually fits
|
|
if (data_size - (namelen) - sizeof(uint32_t)*15 != datalen)
|
|
{
|
|
printk(KERN_ALERT "[chipsec] ERROR: INVALID datalen PARAMETER (%u != %lu)\n", datalen, data_size - namelen - sizeof(uint32_t)*14);
|
|
kfree(kbuf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
// if name overflowed, we only work with the part that fit in kbuf
|
|
name = kzalloc((namelen+1)*sizeof(efi_char16_t), GFP_KERNEL);
|
|
if (!name)
|
|
{
|
|
printk(KERN_ALERT "[chipsec] ERROR: STATUS_UNSUCCESSFUL - could not allocate memory\n" );
|
|
kfree(kbuf);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for(index=0; index < namelen; index++)
|
|
{
|
|
// upper byte is 0x0 (for ASCII), the lower byte is ASCII char
|
|
name[index] = ((efi_char16_t)cptr[index] & 0xFF);
|
|
}
|
|
name[index] = (efi_char16_t)(0);
|
|
|
|
data = (char *) &cptr[namelen];
|
|
|
|
stat = myefi->set_variable(name, &GUID, attr, datalen, data);
|
|
|
|
// clear kbuf before using it for output
|
|
memset(kbuf, '\0', data_size);
|
|
|
|
kbuf[0] = sizeof(uint32_t) * 2;
|
|
kbuf[1] = stat;
|
|
|
|
if (stat != EFI_SUCCESS)
|
|
{
|
|
print_stat(stat);
|
|
}
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)kbuf, sizeof(uint32_t) * 2) > 0)
|
|
{
|
|
kfree(kbuf);
|
|
kfree(name);
|
|
return -EFAULT;
|
|
}
|
|
|
|
kfree(name);
|
|
kfree(kbuf);
|
|
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
case IOCTL_RDMMIO:
|
|
{
|
|
unsigned long addr, first, second;
|
|
char *ioaddr;
|
|
|
|
numargs = 2;
|
|
printk( KERN_INFO "[chipsec] > IOCTL_RDMMIO\n");
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs)) > 0)
|
|
{
|
|
printk( KERN_ALERT "[chipsec] ERROR: STATUS_INVALID_PARAMETER\n" );
|
|
return -EFAULT;
|
|
}
|
|
|
|
addr = (unsigned long)ptr[0];
|
|
ioaddr = my_xlate_dev_mem_ptr(addr);
|
|
|
|
switch(ptr[1])
|
|
{
|
|
case 1:
|
|
ptr[0] = ioread8(ioaddr);
|
|
break;
|
|
case 2:
|
|
ptr[0] = ioread16(ioaddr);
|
|
break;
|
|
case 4:
|
|
ptr[0] = ioread32(ioaddr);
|
|
break;
|
|
#ifdef __x86_64__
|
|
case 8:
|
|
first = ioread32(ioaddr);
|
|
second = ioread32( ioaddr + 4 );
|
|
ptr[0] = first | (second << 32);
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
my_unxlate_dev_mem_ptr(addr, ioaddr);
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
|
|
case IOCTL_WRMMIO:
|
|
{
|
|
unsigned long addr, value, first, second;
|
|
char *ioaddr;
|
|
|
|
numargs = 3;
|
|
printk( KERN_INFO "[chipsec] > IOCTL_WRMMIO\n");
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs)) > 0)
|
|
{
|
|
printk( KERN_ALERT "[chipsec] ERROR: STATUS_INVALID_PARAMETER\n" );
|
|
return -EFAULT;
|
|
}
|
|
|
|
addr = (unsigned long)ptr[0];
|
|
value = (unsigned long)ptr[2];
|
|
ioaddr = my_xlate_dev_mem_ptr(addr);
|
|
|
|
switch(ptr[1])
|
|
{
|
|
case 1:
|
|
iowrite8(value, ioaddr);
|
|
break;
|
|
case 2:
|
|
iowrite16(value, ioaddr);
|
|
break;
|
|
case 4:
|
|
iowrite32(value, ioaddr);
|
|
break;
|
|
case 8:
|
|
#ifdef __x86_64__
|
|
first = value & 0xFFFFFFFF;
|
|
second = (value >> 32) & 0xFFFFFFFF;
|
|
|
|
iowrite32(first, ioaddr);
|
|
iowrite32(second, ioaddr + 4);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
my_unxlate_dev_mem_ptr(addr, ioaddr);
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
|
|
case IOCTL_VA2PA:
|
|
{
|
|
//IN params: va
|
|
//OUT params: pa
|
|
#ifdef CONFIG_X86
|
|
PHYSICAL_ADDRESS pa;
|
|
numargs = 1;
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
|
|
pa.quadpart = virt_to_phys((void*)ptr[0]);
|
|
ptr[0] = pa.quadpart;
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
|
|
case IOCTL_HYPERCALL:
|
|
{
|
|
numargs = 11;
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(ptrbuf[0]) * numargs)) > 0)
|
|
{
|
|
printk( KERN_ALERT "[chipsec] ERROR: STATUS_INVALID_PARAMETER\n" );
|
|
return -EFAULT;
|
|
}
|
|
|
|
#ifdef HYPERCALL_DEBUG
|
|
printk( KERN_DEBUG "[chipsec] > IOCTL_HYPERCALL\n");
|
|
#ifdef __x86_64__
|
|
printk( KERN_DEBUG " RAX = 0x%016lX RBX = 0x%016lX\n", ptrbuf[6], ptrbuf[7] );
|
|
printk( KERN_DEBUG " RCX = 0x%016lX RDX = 0x%016lX\n", ptrbuf[0], ptrbuf[1] );
|
|
printk( KERN_DEBUG " RDI = 0x%016lX RSI = 0x%016lX\n", ptrbuf[8], ptrbuf[9] );
|
|
printk( KERN_DEBUG " R8 = 0x%016lX R9 = 0x%016lX\n", ptrbuf[2], ptrbuf[3] );
|
|
printk( KERN_DEBUG " R10 = 0x%016lX R11 = 0x%016lX\n", ptrbuf[4], ptrbuf[5] );
|
|
#else
|
|
printk( KERN_DEBUG " EAX = 0x%08lX EBX = 0x%08lX ECX = 0x%08lX\n", ptrbuf[6], ptrbuf[7], ptrbuf[0] );
|
|
printk( KERN_DEBUG " EDX = 0x%08lX ESI = 0x%08lX EDI = 0x%08lX\n", ptrbuf[1], ptrbuf[8], ptrbuf[9] );
|
|
#endif
|
|
printk( KERN_DEBUG " XMM0-XMM5 buffer VA = 0x%016lX\n", ptrbuf[10] );
|
|
printk( KERN_DEBUG " Hypercall page VA = 0x%016lX\n", ptrbuf[11]);
|
|
#endif
|
|
|
|
ptrbuf[0] = hypercall(ptrbuf[0], ptrbuf[1], ptrbuf[2], ptrbuf[3], ptrbuf[4], ptrbuf[5], ptrbuf[6], ptrbuf[7], ptrbuf[8], ptrbuf[9], ptrbuf[10], (unsigned long)&hypercall_page_c);
|
|
|
|
#ifdef HYPERCALL_DEBUG
|
|
printk( KERN_DEBUG " Hypercall status = 0x%016lX\n", ptrbuf[0]);
|
|
#endif
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, sizeof(ptrbuf[0])) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
|
|
case IOCTL_MSGBUS_SEND_MESSAGE:
|
|
{
|
|
//IN params: direction, mcr, mcrx, mdr
|
|
//OUT params: mdr_out
|
|
#ifdef CONFIG_X86
|
|
uint32_t direction, mcr, mcrx, mdr, mdr_out;
|
|
numargs = 5;
|
|
printk( KERN_INFO "[chipsec] > IOCTL_MSGBUS_SEND_MESSAGE\n");
|
|
|
|
if(copy_from_user((void*)ptrbuf, (void*)ioctl_param, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
|
|
mdr_out = 0;
|
|
direction = ptr[0];
|
|
mcr = ptr[1];
|
|
mcrx = ptr[2];
|
|
mdr = ptr[3];
|
|
|
|
if (direction & MSGBUS_MDR_IN_MASK)
|
|
// Write data to MDR register
|
|
WritePCICfg( MSGBUS_BUS, MSGBUS_DEV, MSGBUS_FUN, MDR, 4, mdr );
|
|
|
|
// Write extended address to MCRX register if address is > 0xFF
|
|
if (mcrx != 0)
|
|
WritePCICfg( MSGBUS_BUS, MSGBUS_DEV, MSGBUS_FUN, MCRX, 4, mcrx );
|
|
|
|
// Write to MCR register to send the message on the message bus
|
|
WritePCICfg( MSGBUS_BUS, MSGBUS_DEV, MSGBUS_FUN, MCR, 4, mcr );
|
|
|
|
if (direction & MSGBUS_MDR_OUT_MASK) {
|
|
// Read data from MDR register
|
|
mdr_out = ReadPCICfg( MSGBUS_BUS, MSGBUS_DEV, MSGBUS_FUN, MDR, 4 );
|
|
ptr[4] = mdr_out;
|
|
}
|
|
|
|
if(copy_to_user((void*)ioctl_param, (void*)ptrbuf, (sizeof(long) * numargs)) > 0)
|
|
return -EFAULT;
|
|
break;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int open_port(struct inode * inode, struct file * filp)
|
|
{
|
|
return capable(CAP_SYS_RAWIO) ? 0 : -EPERM;
|
|
}
|
|
|
|
static const struct file_operations mem_fops = {
|
|
.llseek = memory_lseek,
|
|
.read = read_mem,
|
|
.write = write_mem,
|
|
.mmap = mmap_mem,
|
|
.open = open_port,
|
|
.unlocked_ioctl = d_ioctl,
|
|
.get_unmapped_area = get_unmapped_area_mem,
|
|
};
|
|
|
|
static struct miscdevice chipsec_dev = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "chipsec",
|
|
.fops = &mem_fops
|
|
};
|
|
|
|
/*
|
|
* 0ld dog never die:
|
|
* https://gist.githubusercontent.com/GoldenOak/a8cd563d671af04a3d387d198aa3ecf8/raw/8dcc90dbbf9b9ffd65cc2c03f1cd48445b84c2b6/obtain_syscall_table_by_proc.c
|
|
*
|
|
* Using kernel_read directly was disabled in Linux 5.10 with commit
|
|
* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4d03e3cc59828c82ee89ea6e27a2f3cdf95aaadf
|
|
* because /proc/kallsyms does not implement ->f_op->read_iter.
|
|
*/
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0) && LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0)
|
|
|
|
static unsigned long chipsec_lookup_name(const char *name)
|
|
{
|
|
unsigned int i = 0, first_space_idx = 0, second_space_idx = 0; /* Read Index and indexes of spaces */
|
|
struct file *proc_ksyms = NULL;
|
|
loff_t pos = 0;
|
|
unsigned long ret = 0;
|
|
ssize_t read = 0;
|
|
int err = 0;
|
|
const size_t name_len = strlen(name);
|
|
|
|
/*
|
|
* Buffer for each line of kallsyms file.
|
|
* The symbol names are limited to KSYM_NAME_LEN=128. When Linux is
|
|
* compiled with clang's Control Flow Integrity, there are large symbols
|
|
* such as
|
|
* __typeid__ZTSFvPvP15ieee80211_local11set_key_cmdP21ieee80211_sub_if_dataP13ieee80211_staP18ieee80211_key_confE_global_addr
|
|
* which lead to a line with 142 characters.
|
|
* Some use a buffer which can hold 256 characters, to be safe.
|
|
*/
|
|
char proc_ksyms_entry[256] = {0};
|
|
|
|
proc_ksyms = filp_open("/proc/kallsyms", O_RDONLY, 0);
|
|
if (proc_ksyms == NULL)
|
|
goto cleanup;
|
|
|
|
read = kernel_read(proc_ksyms, proc_ksyms_entry + i, 1, &pos);
|
|
while (read == 1) {
|
|
if (proc_ksyms_entry[i] == '\n' || (size_t)i == sizeof(proc_ksyms_entry) - 1) {
|
|
/* Prefix-match the name with the 3rd field of the line, after the second space */
|
|
if (second_space_idx > 0 &&
|
|
second_space_idx + 1 + name_len <= sizeof(proc_ksyms_entry) &&
|
|
!strncmp(proc_ksyms_entry + second_space_idx + 1, name, name_len)) {
|
|
printk(KERN_INFO "[+] %s: %.*s\n", name,
|
|
i, proc_ksyms_entry);
|
|
/* Decode the address, which is in hexadecimal */
|
|
proc_ksyms_entry[first_space_idx] = '\0';
|
|
err = kstrtoul(proc_ksyms_entry, 16, &ret);
|
|
if (err) {
|
|
printk(KERN_ERR "kstrtoul returned error %d while parsing %.*s\n",
|
|
err, first_space_idx, proc_ksyms_entry);
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
goto cleanup;
|
|
}
|
|
|
|
i = 0;
|
|
first_space_idx = 0;
|
|
second_space_idx = 0;
|
|
memset(proc_ksyms_entry, 0, sizeof(proc_ksyms_entry));
|
|
} else {
|
|
if (proc_ksyms_entry[i] == ' ') {
|
|
if (first_space_idx == 0) {
|
|
first_space_idx = i;
|
|
} else if (second_space_idx == 0) {
|
|
second_space_idx = i;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
read = kernel_read(proc_ksyms, proc_ksyms_entry + i, 1, &pos);
|
|
}
|
|
printk(KERN_ERR "symbol not found in kallsyms: %s\n", name);
|
|
|
|
cleanup:
|
|
if (proc_ksyms != NULL)
|
|
filp_close(proc_ksyms, 0);
|
|
return ret;
|
|
}
|
|
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5,10,0)
|
|
|
|
static struct kprobe kp = {
|
|
.symbol_name = "kallsyms_lookup_name",
|
|
.flags = KPROBE_FLAG_DISABLED
|
|
};
|
|
|
|
static unsigned long chipsec_lookup_name_scinit(const char *name)
|
|
{
|
|
unsigned long (*chipsec_lookup_name_fp)(const char *name) = NULL;
|
|
int kp_ret;
|
|
|
|
// try kprobes first, but have a fallback as they might be disabled
|
|
kp_ret = register_kprobe(&kp);
|
|
if (kp_ret < 0) {
|
|
dbgprint("register_kprobe failed, returned %d", kp_ret);
|
|
} else {
|
|
chipsec_lookup_name_fp = (unsigned long (*) (const char *name))kp.addr;
|
|
unregister_kprobe(&kp);
|
|
}
|
|
|
|
// brute force by doing a symbolic search via sprint_symbol
|
|
if (!chipsec_lookup_name_fp) {
|
|
char name[KSYM_SYMBOL_LEN];
|
|
unsigned long start = (unsigned long) sprint_symbol;
|
|
unsigned long end = start - 32 * 1024;
|
|
unsigned long addr, offset;
|
|
char *off_ptr;
|
|
|
|
for (addr = start; addr > end; addr--) {
|
|
if (sprint_symbol(name, addr) <= 0)
|
|
break;
|
|
if (!strncmp(name, "0x", 2))
|
|
break;
|
|
off_ptr = strchr(name, '+');
|
|
if (!off_ptr)
|
|
break;
|
|
if (sscanf(off_ptr, "+%lx", &offset) != 1)
|
|
break;
|
|
addr -= offset;
|
|
if (off_ptr - name == 20 &&
|
|
!strncmp(name, "kallsyms_lookup_name", 20))
|
|
{
|
|
chipsec_lookup_name_fp = (void *)addr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!chipsec_lookup_name_fp)
|
|
dbgprint("lookup via sprint_symbol() failed, too");
|
|
}
|
|
|
|
if (chipsec_lookup_name_fp) {
|
|
static_call_update(chipsec_lookup_name_sc, chipsec_lookup_name_fp);
|
|
return static_call(chipsec_lookup_name_sc)(name);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long chipsec_lookup_name(const char *name)
|
|
{
|
|
return static_call(chipsec_lookup_name_sc)(name);
|
|
}
|
|
|
|
#else
|
|
|
|
static unsigned long chipsec_lookup_name(const char *name){
|
|
return kallsyms_lookup_name(name);
|
|
}
|
|
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,10,0)
|
|
|
|
static int chipsec_page_is_ram_scinit(unsigned long pagenr)
|
|
{
|
|
BUG_ON(guess_page_is_ram == NULL); // resolved in find_symbols()
|
|
static_call_update(chipsec_page_is_ram_sc, guess_page_is_ram);
|
|
return static_call(chipsec_page_is_ram_sc)(pagenr);
|
|
}
|
|
|
|
static int chipsec_page_is_ram(unsigned long pagenr)
|
|
{
|
|
return static_call(chipsec_page_is_ram_sc)(pagenr);
|
|
}
|
|
|
|
#else
|
|
|
|
static int chipsec_page_is_ram(unsigned long pagenr)
|
|
{
|
|
BUG_ON(guess_page_is_ram == NULL); // resolved in find_symbols()
|
|
return guess_page_is_ram(pagenr);
|
|
}
|
|
|
|
#endif
|
|
|
|
int find_symbols(void)
|
|
{
|
|
//Older kernels don't have kallsyms_lookup_name. Use FMEM method (pass from run.sh)
|
|
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,33)
|
|
printk("Chipsec warning: Using function addresses provided by run.sh");
|
|
guess_page_is_ram=(void *)a1;
|
|
dbgprint ("set guess_page_is_ram: %p",guess_page_is_ram);
|
|
#ifdef __HAVE_PHYS_MEM_ACCESS_PROT
|
|
guess_phys_mem_access_prot=(void *)a2;
|
|
dbgprint ("set guess_phys_mem_acess_prot: %p",guess_phys_mem_access_prot);
|
|
#else
|
|
guess_phys_mem_access_prot = &cs_phys_mem_access_prot;
|
|
#endif
|
|
#else
|
|
guess_page_is_ram = (void *)chipsec_lookup_name("page_is_ram");
|
|
#ifdef __HAVE_PHYS_MEM_ACCESS_PROT
|
|
guess_phys_mem_access_prot = (void *)chipsec_lookup_name("phys_mem_access_prot");
|
|
#else
|
|
guess_phys_mem_access_prot = &cs_phys_mem_access_prot;
|
|
#endif
|
|
#endif
|
|
if (guess_page_is_ram == 0 || guess_phys_mem_access_prot == 0) {
|
|
printk("Chipsec find_symbols failed. Unloading module");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/// Function executed upon loading module
|
|
int __init
|
|
init_module (void)
|
|
{
|
|
int ret = 0;
|
|
printk(KERN_ALERT "Chipsec module loaded \n");
|
|
printk(KERN_ALERT "** This module exposes hardware & memory access, **\n");
|
|
printk(KERN_ALERT "** which can effect the secure operation of **\n");
|
|
printk(KERN_ALERT "** production systems!! Use for research only! **\n");
|
|
|
|
ret = find_symbols();
|
|
if (ret)
|
|
{
|
|
printk("Chipsec symbol lookup failed\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
ret = misc_register(&chipsec_dev);
|
|
if (ret)
|
|
{
|
|
printk("Unable to register the chipsec device\n");
|
|
return ret;
|
|
}
|
|
|
|
// initialize allocated mem list
|
|
INIT_LIST_HEAD(&allocated_mem_list.list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/// Function executed when unloading module
|
|
void cleanup_module (void)
|
|
{
|
|
struct list_head *pos, *q;
|
|
struct allocated_mem_list *element;
|
|
|
|
dbgprint ("Destroying chipsec device");
|
|
misc_deregister(&chipsec_dev);
|
|
dbgprint ("exit");
|
|
|
|
// freeing
|
|
list_for_each_safe(pos, q, &allocated_mem_list.list) {
|
|
element = list_entry(pos, struct allocated_mem_list, list);
|
|
printk(KERN_INFO "auto freeing allocated memory (va = %p, pa = 0x%llx)\n", element->va, element->pa);
|
|
kfree(element->va);
|
|
list_del(pos);
|
|
kfree(element);
|
|
}
|
|
}
|