Skip to content

Commit

Permalink
Add platform-dependent RTC emulation & improve BIOS shimming stability
Browse files Browse the repository at this point in the history
  • Loading branch information
ttg-public committed Jan 1, 1970
1 parent 8d1f6b7 commit 76c36a4
Show file tree
Hide file tree
Showing 9 changed files with 340 additions and 36 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -761,4 +761,4 @@ add_definitions(-DCONFIG_OID_REGISTRY)
add_definitions(-DCONFIG_UCS2_STRING)

add_executable(redpill
redpill_main.c redpill_main.h internal/call_protected.c internal/call_protected.h common.h config/cmdline_delegate.c config/cmdline_delegate.h shim/boot_device_shim.c shim/boot_device_shim.h internal/stealth.c internal/stealth.h config/runtime_config.c config/runtime_config.h test.c shim/bios_shim.c shim/bios_shim.h internal/override_symbol.c internal/override_symbol.h shim/bios/bios_shims_collection.c shim/bios/bios_shims_collection.h shim/block_fw_update_shim.c shim/block_fw_update_shim.h internal/intercept_execve.c internal/intercept_execve.h shim/disable_exectutables.c shim/disable_exectutables.h debug/debug_execve.c debug/debug_execve.h compat/string_compat.c compat/string_compat.h internal/stealth/sanitize_cmdline.c internal/stealth/sanitize_cmdline.h internal/virtual_pci.c internal/virtual_pci.h shim/pci_shim.c shim/pci_shim.h)
redpill_main.c redpill_main.h internal/call_protected.c internal/call_protected.h common.h config/cmdline_delegate.c config/cmdline_delegate.h shim/boot_device_shim.c shim/boot_device_shim.h internal/stealth.c internal/stealth.h config/runtime_config.c config/runtime_config.h test.c shim/bios_shim.c shim/bios_shim.h internal/override_symbol.c internal/override_symbol.h shim/bios/bios_shims_collection.c shim/bios/bios_shims_collection.h shim/block_fw_update_shim.c shim/block_fw_update_shim.h internal/intercept_execve.c internal/intercept_execve.h shim/disable_exectutables.c shim/disable_exectutables.h debug/debug_execve.c debug/debug_execve.h compat/string_compat.c compat/string_compat.h internal/stealth/sanitize_cmdline.c internal/stealth/sanitize_cmdline.h internal/virtual_pci.c internal/virtual_pci.h shim/pci_shim.c shim/pci_shim.h shim/bios/rtc.c shim/bios/rtc.h shim/bios/rtc_proxy.c shim/bios/rtc_proxy.h)
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ SRCS-y += compat/string_compat.c \
internal/override_symbol.c internal/intercept_execve.c internal/call_protected.c \
internal/stealth/sanitize_cmdline.c internal/stealth.c internal/virtual_pci.c \
config/cmdline_delegate.c config/runtime_config.c \
shim/boot_device_shim.c shim/bios/bios_shims_collection.c shim/bios_shim.c shim/block_fw_update_shim.c \
shim/disable_exectutables.c shim/pci_shim.c \
shim/boot_device_shim.c shim/bios/rtc_proxy.c shim/bios/bios_shims_collection.c shim/bios_shim.c \
shim/block_fw_update_shim.c shim/disable_exectutables.c shim/pci_shim.c \
redpill_main.c
OBJS = $(SRCS-y:.c=.o)
#this module name CAN NEVER be the same as the main file (or it will get weird ;)) and the main file has to be included
Expand Down
2 changes: 1 addition & 1 deletion redpill_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ static int __init init_redpill(void)
|| (out = populate_runtime_config(&current_config)) != 0 //This MUST be second
|| (out = register_boot_shim(&current_config.boot_media, &current_config.mfg_mode)) //Make sure we're quick with this one
|| (out = register_execve_interceptor()) != 0 //Register this reasonably high as other modules can use it blindly
|| (out = register_bios_shim()) != 0
|| (out = register_bios_shim(current_config.hw_config)) != 0
|| (out = disable_common_executables()) != 0
|| (out = register_fw_update_shim()) != 0
#ifndef DISABLE_UNLOADABLE
Expand Down
62 changes: 39 additions & 23 deletions shim/bios/bios_shims_collection.c
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#include "bios_shims_collection.h"
#include "mfgbios_types.h"
#include "../../config/runtime_config.h" //hw_config
#include "rtc_proxy.h"
#include "../../common.h"

static unsigned long shimmed_entries[VTK_SIZE] = { '\0' };
static unsigned long org_shimmed_entries[VTK_SIZE] = { '\0' }; //original entries which were shimmed by custom entries
static unsigned long cust_shimmed_entries[VTK_SIZE] = { '\0' }; //custom entries which were set as shims

static unsigned long shim_null_zero_ulong(void) { return 0; }
static void shim_null_void(void) { }
Expand All @@ -16,17 +19,19 @@ static void inline shim_entry(unsigned long *vtable_start, const unsigned int id
return;
}

//@todo OK, this is buggy - if you shim an entry which is not present in the original vtable (i.e. 0000000000000000)
// you will never be able to recover it using unshim... but you shouldn't touch such entries anyway
if (unlikely(shimmed_entries[idx])) {
pr_loc_wrn("Index %d already shimmed - will be replaced (possible bug?)", idx);
} else {
shimmed_entries[idx] = vtable_start[idx]; //Only save original-original entry (not the override one)
}
//The vtable entry is either not shimmed OR already shimmed with what we set before OR already *was* shimmed but
// external (i.e. mfgBIOS) code overrode the shimmed entry.
//We only save the original entry if it was set by the mfgBIOS (so not shimmed yet or ext. override situation)

//it was already shimmed and the shim is still there => noop
if (cust_shimmed_entries[idx] && cust_shimmed_entries[idx] == vtable_start[idx])
return;

pr_loc_dbg("mfgBIOS vtable [%d] originally %ps<%p> will now be %ps<%p>", idx, (void *) shimmed_entries[idx],
(void *) shimmed_entries[idx], new_sym_ptr, new_sym_ptr);
vtable_start[idx] = (unsigned long) new_sym_ptr;
pr_loc_dbg("mfgBIOS vtable [%d] originally %ps<%p> will now be %ps<%p>", idx, (void *) vtable_start[idx],
(void *) vtable_start[idx], new_sym_ptr, new_sym_ptr);
org_shimmed_entries[idx] = vtable_start[idx];
cust_shimmed_entries[idx] = (unsigned long)new_sym_ptr;
vtable_start[idx] = cust_shimmed_entries[idx];
}

/**
Expand Down Expand Up @@ -59,9 +64,11 @@ static void print_debug_symbols(unsigned long *vtable_start, unsigned long *vtab
/**
* Applies shims to the vtable used by the bios
*
* These calls may execute multiple times as the mfgBIOS is loading.
*
* @return true when shimming succeeded, false otherwise
*/
bool shim_bios(struct module *mod, unsigned long *vtable_start, unsigned long *vtable_end)
bool shim_bios(const struct hw_config *hw, struct module *mod, unsigned long *vtable_start, unsigned long *vtable_end)
{
if (unlikely(!vtable_start)) {
pr_loc_bug("%s called without vtable start populated?!", __FUNCTION__);
Expand All @@ -86,13 +93,17 @@ bool shim_bios(struct module *mod, unsigned long *vtable_start, unsigned long *v
shim_entry(vtable_start, VTK_GET_MICROP_ID, shim_null_zero_ulong);
shim_entry(vtable_start, VTK_SET_MICROP_ID, shim_null_zero_ulong);

//DS918+ only [but it should be safe to set them anyway ;)]
shim_entry(vtable_start, VTK_RTC_SET_APWR, shim_null_zero_ulong);
shim_entry(vtable_start, VTK_RTC_GET_APWR, shim_null_zero_ulong);
shim_entry(vtable_start, VTK_RTC_INT_APWR, shim_null_void);
shim_entry(vtable_start, VTK_RTC_UINT_APWR, shim_null_void);
// shim_entry(vtable_start, VTK_RTC_GET_TIME, TODO);
// shim_entry(vtable_start, VTK_RTC_SET_TIME, TODO);
if (hw->emulate_rtc) {
pr_loc_dbg("Platform requires RTC proxy - enabling");
shim_entry(vtable_start, VTK_RTC_GET_TIME, rtc_proxy_get_time);
shim_entry(vtable_start, VTK_RTC_SET_TIME, rtc_proxy_set_time);
shim_entry(vtable_start, VTK_RTC_INT_APWR, rtc_proxy_init_auto_power_on);
shim_entry(vtable_start, VTK_RTC_GET_APWR, rtc_proxy_get_auto_power_on);
shim_entry(vtable_start, VTK_RTC_SET_APWR, rtc_proxy_set_auto_power_on);
shim_entry(vtable_start, VTK_RTC_UINT_APWR, rtc_proxy_uinit_auto_power_on);
} else {
pr_loc_dbg("Native RTC supported - not enabling proxy (emulate_rtc=%d)", hw->emulate_rtc ? 1:0);
}

print_debug_symbols(vtable_start, vtable_end);

Expand All @@ -102,18 +113,23 @@ bool shim_bios(struct module *mod, unsigned long *vtable_start, unsigned long *v
bool unshim_bios(unsigned long *vtable_start, unsigned long *vtable_end)
{
for (int i = 0; i < VTK_SIZE; i++) {
if (!shimmed_entries[i])
//make sure to check the cust_ one as org_ may contain NULL ptrs and we should restore them as NULL if they were
// so originally
if (!cust_shimmed_entries[i])
continue;

pr_loc_dbg("Restoring vtable [%d] from %ps<%p> to %ps<%p>", i, (void *) vtable_start[i],
(void *) vtable_start[i], (void *) shimmed_entries[i], (void *) shimmed_entries[i]);
vtable_start[i] = shimmed_entries[i];
(void *) vtable_start[i], (void *) org_shimmed_entries[i], (void *) org_shimmed_entries[i]);
vtable_start[i] = org_shimmed_entries[i];
}

clean_shims_history();

return true;
}

void clean_shims_history(void)
{
memset(shimmed_entries, 0, sizeof(shimmed_entries));
memset(org_shimmed_entries, 0, sizeof(org_shimmed_entries));
memset(cust_shimmed_entries, 0, sizeof(cust_shimmed_entries));
}
4 changes: 3 additions & 1 deletion shim/bios/bios_shims_collection.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
#include <linux/types.h> //bool
#include <linux/module.h> //struct module


typedef struct hw_config hw_config_bios_shim_col;
/**
* Insert all the shims to the mfgBIOS
*/
bool shim_bios(struct module *mod, unsigned long *vtable_start, unsigned long *vtable_end);
bool shim_bios(const hw_config_bios_shim_col *hw, struct module *mod, unsigned long *vtable_start, unsigned long *vtable_end);

/**
* Removes all shims from the mfgBIOS
Expand Down
235 changes: 235 additions & 0 deletions shim/bios/rtc_proxy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/*
* Proxy between an ACPI RTC and mfgBIOS calls
*
* Some platforms don't use a standard RTC chip but implement a custom platform-specific one. To handle different chips
* mfgBIOS uses a standardized interface. This works perfectly fine when mfgBIOS expects an ACPI-complaint RTC to be
* present. However, it does not work when a given platform is expected to contain some 3rd-party I2C clock chip.
*
* Motorola MC146818 was a de facto standard RTC chip when PC/AT emerged. Later on other clones started emulating the
* interface. This become so prevalent that ACPI standardized the basic interface of RTC on PC-compatibile systems as
* MC146818 interface. Thus, this module assumes that mfgBIOS calls can be proxied to MC146818 interface (which will
* work on any ACPI-complaint system and any sane hypervisor).
*
* As some of the functions are rarely used (and often even completely broken on many systems), like RTC wakeup they're
* not really implemented but instead mocked to look "just good enough".
*
* References:
* - https://www.kernel.org/doc/html/latest/admin-guide/rtc.html
* - https://embedded.fm/blog/2018/6/5/an-introduction-to-bcd
*/
#include <linux/mc146818rtc.h>
#include <linux/bcd.h>
#include "rtc_proxy.h"
#include "../../common.h"

//Confused? See https://slate.com/technology/2016/02/the-math-behind-leap-years.html
#define year_is_leap(year) !((year)%((year)%25?4:16))
#define mfg_year_to_full(val) ((val)+1900) //MfgCompatTime counts years as offset from 1900
#define mfg_month_to_normal(val) ((val)+1) //MfgCompatTime has 0-based months
#define normal_month_to_mfg(val) ((val)-1)
static const unsigned char months_to_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

static struct MfgCompatAutoPwrOn *auto_power_on_mock = NULL;

inline static void debug_print_mfg_time(struct MfgCompatTime *mfgTime)
{
pr_loc_dbg("MfgCompatTime raw data: sec=%u min=%u hr=%u wkd=%u day=%u mth=%u yr=%u", mfgTime->second,
mfgTime->minute, mfgTime->hours, mfgTime->wkday, mfgTime->day, mfgTime->month, mfgTime->year);
}

/**
* Standardizes & abstracts RTC reading
*
* Reading & writing RTC requires conversion of values based on some registers and chips. This function simply accept
* pointers to YY-MM-DD WeekDay HHmmss values and does all the conversions for you after reading.
*/
static void read_rtc_num(unsigned char *yy, unsigned char *mm, unsigned char *dd, unsigned char *wd, unsigned char *hr,
unsigned char *mi, unsigned char *ss)
{
//As the clock uses IRQ 8 normally we need to atomically stop it to read all values and restore it later
unsigned long flags;
spin_lock_irqsave(&rtc_lock, flags);
const unsigned char rtc_control = CMOS_READ(RTC_CONTROL);

//There are two formats how RTCs can report time: normal numbers or an ancient BCD. Currently (at least in Linux v4)
//BCD is always used for MC146818 (but this can change). This we need to handle both cases.
if (likely(RTC_ALWAYS_BCD) || (rtc_control & RTC_DM_BINARY)) { //a common idiom, search for RTC_ALWAYS_BCD in kernel
pr_loc_dbg("Reading BCD-based RTC");
*yy = bcd2bin(CMOS_READ(RTC_YEAR));
*mm = bcd2bin(CMOS_READ(RTC_MONTH));
*dd = bcd2bin(CMOS_READ(RTC_DAY_OF_MONTH));
*wd = bcd2bin(CMOS_READ(RTC_DAY_OF_WEEK));
*hr = bcd2bin(CMOS_READ(RTC_HOURS));
*mi = bcd2bin(CMOS_READ(RTC_MINUTES));
*ss = bcd2bin(CMOS_READ(RTC_SECONDS));
} else {
pr_loc_dbg("Reading binary-based RTC");
*yy = CMOS_READ(RTC_YEAR);
*mm = CMOS_READ(RTC_MONTH);
*dd = CMOS_READ(RTC_DAY_OF_MONTH);
*wd = CMOS_READ(RTC_DAY_OF_WEEK);
*hr = CMOS_READ(RTC_HOURS);
*mi = CMOS_READ(RTC_MINUTES);
*ss = CMOS_READ(RTC_SECONDS);
}
spin_unlock_irqrestore(&rtc_lock, flags);
}

/**
* Standardizes & abstracts RTC time setting
*
* Reading & writing RTC requires conversion of values based on some registers and chips. This function simply accepts
* values to be set in YY-MM-DD WeekDay HHmmss format and does all the conversions/locking/freq resets for you.
*/
static void write_rtc_num(unsigned char yy, unsigned char mm, unsigned char dd, unsigned char wd, unsigned char hr,
unsigned char mi, unsigned char ss)
{
unsigned long flags;
spin_lock_irqsave(&rtc_lock, flags);
unsigned char rtc_control = CMOS_READ(RTC_CONTROL); //RTC control register value locked for us
unsigned char rtc_freq_tick = CMOS_READ(RTC_FREQ_SELECT);
CMOS_WRITE((rtc_control|RTC_SET), RTC_CONTROL); //enter clock setting state
CMOS_WRITE(rtc_freq_tick|RTC_DIV_RESET2, RTC_FREQ_SELECT); //this should reset the ticks

if (likely(RTC_ALWAYS_BCD) || (rtc_control & RTC_DM_BINARY)) { //a common idiom, search for RTC_ALWAYS_BCD in kernel
pr_loc_dbg("Writing BCD-based RTC");
CMOS_WRITE(bin2bcd(yy), RTC_YEAR);
CMOS_WRITE(bin2bcd(mm), RTC_MONTH);
CMOS_WRITE(bin2bcd(dd), RTC_DAY_OF_MONTH);
CMOS_WRITE(bin2bcd(wd), RTC_DAY_OF_WEEK);
CMOS_WRITE(bin2bcd(hr), RTC_HOURS);
CMOS_WRITE(bin2bcd(mi), RTC_MINUTES);
CMOS_WRITE(bin2bcd(ss), RTC_SECONDS);
} else {
pr_loc_dbg("Writing binary-based RTC");
CMOS_WRITE(yy, RTC_YEAR);
CMOS_WRITE(mm, RTC_MONTH);
CMOS_WRITE(dd, RTC_DAY_OF_MONTH);
CMOS_WRITE(wd, RTC_DAY_OF_WEEK);
CMOS_WRITE(hr, RTC_HOURS);
CMOS_WRITE(mi, RTC_MINUTES);
CMOS_WRITE(ss, RTC_SECONDS);
}

CMOS_WRITE(rtc_control, RTC_CONTROL); //restore original control register
CMOS_WRITE(rtc_freq_tick, RTC_FREQ_SELECT); //...and the ticks too
spin_unlock_irqrestore(&rtc_lock, flags);
}

int rtc_proxy_get_time(struct MfgCompatTime *mfgTime)
{
if (mfgTime == NULL) {
pr_loc_wrn("Got an invalid call to %s", __FUNCTION__);
return -EPERM;
}

debug_print_mfg_time(mfgTime);

unsigned char rtc_year; //mfgTime uses offset from 1900 while RTC uses 2-digit format (see below)
unsigned char rtc_month; //mfgTime uses 0-11 while RTC uses 1-12
read_rtc_num(&rtc_year, &rtc_month, &mfgTime->day, &mfgTime->wkday, &mfgTime->hours, &mfgTime->minute,
&mfgTime->second);

//So yeah, it's 2021 and PC RTCs still use 2 digit year so we have to do a classic Y2K hack with epoch
//RTC nowadays is assumed to have a range of 1970-2069 which forces two assumptions:
// - Values 0-69 indicate 2000-2069
// - Values 70-99 indicate 1970-1999
//As the mfgTime->year uses value of years since 1900 without magic rollovers we need to correct it by 100 for the
//2000s epoch. Search for e.g. "mc146818_decode_year" in Linux, that's (sadly) a common method.
mfgTime->year = (likely(rtc_year < 70)) ? rtc_year + 100 : rtc_year;
mfgTime->month = normal_month_to_mfg(rtc_month);

pr_info("Time got from RTC is %4d-%02d-%02d %2d:%02d:%02d (UTC)", mfg_year_to_full(mfgTime->year),
mfg_month_to_normal(mfgTime->month), mfgTime->day, mfgTime->hours, mfgTime->minute, mfgTime->second);
debug_print_mfg_time(mfgTime);

return 0;
}

int rtc_proxy_set_time(struct MfgCompatTime *mfgTime)
{
if (mfgTime == NULL) {
pr_loc_wrn("Got an invalid call to %s", __FUNCTION__);
return -EPERM;
}

debug_print_mfg_time(mfgTime);

//Ok, this is PROBABLY not needed but we don't want to crash the RTC if an invalid value is passed to this function
//Also, we are aware of leap seconds but do you think 1984 hardware is? (spoiler alert: no)
if (unlikely(mfgTime->second > 59 || mfgTime->minute > 59 || mfgTime->hours > 24 || mfgTime->wkday > 6 ||
mfgTime->day == 0 || mfgTime->month > 11)) {
pr_loc_wrn("Got invalid generic RTC data in %s", __FUNCTION__);
return -EINVAL;
}

//Year validation needs to take leap years into account. This code can be shorter but it's expended for readability
if (unlikely(mfgTime->month == 1 && year_is_leap(mfgTime->year))) {
if (mfgTime->day != (months_to_days[mfgTime->month] + 1)) {
pr_loc_wrn("Invalid RTC leap year day of month in %s", __FUNCTION__);
return -EINVAL;
}
} else if(mfgTime->day != (months_to_days[mfgTime->month])) {
pr_loc_wrn("Invalid RTC regular year day of month in %s", __FUNCTION__);
return -EINVAL;
}

//mfgTime->year uses a positive offset since 1900. However, ACPI-complain RTC cannot handle range higher than
//1970-2069 (see comment in rtc_proxy_get_time()).
unsigned char rtc_year = mfgTime->year; //mfgTime uses offset from 1900 while RTC uses 2-digit format (see below)
if (unlikely(rtc_year > 169)) { //This cannot be valid as RTC cannot handle >2069
pr_loc_wrn("Year overflow in %s", __FUNCTION__);
return -EINVAL;
} else if(likely(rtc_year > 100)) {
rtc_year -= 100; //RTC uses 0-69 for 2000s so we need to shift mfgTime 1900-now offset by 100
}

unsigned char rtc_month = mfg_month_to_normal(mfgTime->month); //mfgTime uses 0-11 while RTC uses 1-12

write_rtc_num(rtc_year, rtc_month, mfgTime->day, mfgTime->wkday, mfgTime->hours, mfgTime->minute, mfgTime->second);

pr_info("RTC time set to %4d-%02d-%02d %2d:%02d:%02d (UTC)", mfg_year_to_full(mfgTime->year),
mfg_month_to_normal(mfgTime->month), mfgTime->day, mfgTime->hours, mfgTime->minute, mfgTime->second);

return 0;
}

int rtc_proxy_init_auto_power_on(void)
{
pr_loc_dbg("RTC power-on \"enabled\" via %s", __FUNCTION__);

return 0;
}

int rtc_proxy_get_auto_power_on(struct MfgCompatAutoPwrOn *mfgPwrOn)
{
if (auto_power_on_mock == NULL || auto_power_on_mock->num < 1) {
pr_loc_wrn("Got an invalid call to %s", __FUNCTION__);
return -EPERM;
}

pr_loc_dbg("Mocking auto-power GET on RTC");
memcpy(auto_power_on_mock, mfgPwrOn, sizeof(struct MfgCompatAutoPwrOn));

return 0;
}

int rtc_proxy_set_auto_power_on(struct MfgCompatAutoPwrOn *mfgPwrOn)
{
if (mfgPwrOn == NULL || mfgPwrOn->num < 1) { //That's just either a bogus call or a stupid call
pr_loc_wrn("Got an invalid call to %s", __FUNCTION__);
return -EPERM;
}

pr_loc_dbg("Mocking auto-power SET on RTC");
memcpy(mfgPwrOn, auto_power_on_mock, sizeof(struct MfgCompatAutoPwrOn));

return 0;
}

int rtc_proxy_uinit_auto_power_on(void)
{
pr_loc_dbg("RTC power-on \"disabled\" via %s", __FUNCTION__);

return 0;
}
Loading

0 comments on commit 76c36a4

Please sign in to comment.