Skip to content

Commit

Permalink
Add kernel console swapping & port ttyS0 registration on broken platf…
Browse files Browse the repository at this point in the history
…orms
  • Loading branch information
ttg-public committed Jan 1, 1970
1 parent 76c36a4 commit c390f74
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 2 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
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 shim/bios/rtc.c shim/bios/rtc.h shim/bios/rtc_proxy.c shim/bios/rtc_proxy.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 internal/virtual_pmu.c internal/virtual_pmu.h internal/virtual_uart.c internal/virtual_uart.h shim/uart_fixer.c shim/uart_fixer.h)
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -12,7 +12,7 @@ SRCS-y += compat/string_compat.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/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 \
shim/block_fw_update_shim.c shim/disable_exectutables.c shim/pci_shim.c shim/uart_fixer.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: 2 additions & 0 deletions config/platforms.h
Expand Up @@ -18,6 +18,7 @@ const struct hw_config supported_platforms[] = {
},
.emulate_rtc = false,
.swap_serial = true,
.reinit_ttyS0 = false,
},
{
.name = "DS918+",
Expand All @@ -40,6 +41,7 @@ const struct hw_config supported_platforms[] = {
},
.emulate_rtc = true,
.swap_serial = false,
.reinit_ttyS0 = true,
},
};

Expand Down
26 changes: 26 additions & 0 deletions config/runtime_config.c
Expand Up @@ -81,6 +81,31 @@ static inline bool validate_nets(const unsigned short if_num, mac_address *macs[
return valid;
}

/**
* This function validates consistency of the currently loaded platform config with the current environment
*
* Some options don't make sense unless the kernel was built with some specific configuration. This function aims to
* detect common pitfalls in platforms configuration. This doesn't so much validate the platform definition per se
* (but partially too) but the match between platform config chosen vs. kernel currently attempting to run that
* platform.
*/
static inline bool validate_platform_config(const struct hw_config *hw)
{
#ifdef CONFIG_SYNO_X86_SERIAL_PORT_SWAP
const bool kernel_serial_swapped = true;
#else
const bool kernel_serial_swapped = false;
#endif

//This will not prevent the code from working, so it's not an error state by itself
if (unlikely(hw->swap_serial && !kernel_serial_swapped))
pr_loc_bug("Your kernel indicates COM1 & COM2 ARE NOT swapped but your platform specifies swapping");
else if(unlikely(!hw->swap_serial && kernel_serial_swapped))
pr_loc_bug("Your kernel indicates COM1 & COM2 ARE swapped but your platform specifies NO swapping");

return true;
}

static int populate_hw_config(struct runtime_config *config)
{
//We cannot run with empty model or model which didn't match
Expand Down Expand Up @@ -110,6 +135,7 @@ static bool validate_runtime_config(const struct runtime_config *config)
valid &= validate_sn(&config->sn);
valid &= validate_vid_pid(&config->boot_media);
valid &= validate_nets(config->netif_num, config->macs);
valid &= validate_platform_config(config->hw_config);

if (valid) {
pr_loc_dbg("Config validation resulted in %s", valid ? "OK" : "ERR");
Expand Down
1 change: 1 addition & 0 deletions config/runtime_config.h
Expand Up @@ -43,6 +43,7 @@ struct hw_config {
//All custom flags
bool emulate_rtc:1;
bool swap_serial:1; //Whether ttyS0 and ttyS1 are swapped (reverses CONFIG_SYNO_X86_SERIAL_PORT_SWAP)
bool reinit_ttyS0:1; //Should the ttyS0 be forcefully re-initialized after module loads
};

struct runtime_config {
Expand Down
3 changes: 3 additions & 0 deletions internal/call_protected.c
Expand Up @@ -78,5 +78,8 @@ DEFINE_UNEXPORTED_SHIM(int, do_execve, CP_LIST(struct filename *filename,
DEFINE_UNEXPORTED_SHIM(struct filename *, getname, CP_LIST(const char __user * filename), CP_LIST(filename), ERR_PTR(-EFAULT));
#endif

DEFINE_UNEXPORTED_SHIM(int, early_serial_setup, CP_LIST(struct uart_port *port), port, -EIO);
DEFINE_UNEXPORTED_SHIM(int, update_console_cmdline, CP_LIST(char *name, int idx, char *name_new, int idx_new, char *options), CP_LIST(name, idx, name_new, idx_new, options), -EIO);

DEFINE_DYNAMIC_SHIM(void, usb_register_notify, CP_LIST(struct notifier_block *nb), CP_LIST(nb), __VOID_RETURN__);
DEFINE_DYNAMIC_SHIM(void, usb_unregister_notify, CP_LIST(struct notifier_block *nb), CP_LIST(nb), __VOID_RETURN__);
4 changes: 4 additions & 0 deletions internal/call_protected.h
Expand Up @@ -34,6 +34,10 @@ CP_DECLARE_SHIM(int, do_execve, CP_LIST(struct filename *filename,
CP_DECLARE_SHIM(struct filename *, getname, CP_LIST(const char __user *));
#endif

typedef struct uart_port *uart_port_p;
CP_DECLARE_SHIM(int, early_serial_setup, CP_LIST(struct uart_port *port));
CP_DECLARE_SHIM(int, update_console_cmdline, CP_LIST(char *name, int idx, char *name_new, int idx_new, char *options));

#include <linux/notifier.h>
void _usb_register_notify(struct notifier_block *nb);
void _usb_unregister_notify(struct notifier_block *nb);
Expand Down
3 changes: 3 additions & 0 deletions redpill_main.c
Expand Up @@ -9,6 +9,7 @@
#include "shim/block_fw_update_shim.h" //Prevent firmware update from running
#include "shim/disable_exectutables.h" //Disable common problematic executables
#include "shim/pci_shim.h" //Handles PCI devices emulation
#include "shim/uart_fixer.h" //Various fixes for UART weirdness

//This (shameful) flag disables shims which cannot be properly unloaded to make debugging of other things easier
//#define DISABLE_UNLOADABLE
Expand All @@ -26,6 +27,7 @@ static int __init init_redpill(void)
if (
(out = extract_config_from_cmdline(&current_config)) != 0 //This MUST be the first entry
|| (out = populate_runtime_config(&current_config)) != 0 //This MUST be second
|| (out = register_uart_fixer(current_config.hw_config)) != 0 //Fix consoles ASAP
|| (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(current_config.hw_config)) != 0
Expand Down Expand Up @@ -64,6 +66,7 @@ static void __exit cleanup_redpill(void)
unregister_bios_shim,
unregister_execve_interceptor,
unregister_boot_shim,
unregister_uart_fixer
};

int out;
Expand Down
183 changes: 183 additions & 0 deletions shim/uart_fixer.c
@@ -0,0 +1,183 @@
#include "uart_fixer.h"
#include "../common.h"
#include "../../config/runtime_config.h" //hw_config
#include "../../internal/call_protected.h" //early_serial_setup(), update_console_cmdline()
#include <asm/serial.h> //flags for pc_com*
#include <linux/serial_core.h> //struct uart_port
#include <linux/serial_8250.h> //serial8250_unregister_port
#include <linux/console.h> //console_lock(), console_unlock()
#include <linux/sched.h> //for_each_process, kill_pgrp

//They changed name of flags const: https://github.com/torvalds/linux/commit/196cf358422517b3ff3779c46a1f3e26fb084172
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0)
#define STD_COMX_FLAGS STD_COM_FLAGS
#endif

#define CONSOLE_NAME_UART "ttyS"
#define CONSOLE_BAUD_UART "115200n8"
//These definitions are taken from asm/serial.h for a normal (i.e. non-swapped) UART1/COM1 port on an x86 PC
static const struct uart_port pc_com1_port = {
.iobase = 0x3f8,
.uartclk = BASE_BAUD,
.irq = 4,
.flags = STD_COMX_FLAGS
};
static const struct uart_port pc_com2_port = {
.iobase = 0x2f8,
.uartclk = BASE_BAUD,
.irq = 3,
.flags = STD_COMX_FLAGS
};

static bool serial_swapped = false; //Whether ttyS0 and ttyS1 were swapped
static bool ttyS0_force_initted = false; //Was ttyS0 forcefully initialized by us?

/**
* After re-registering a console any TTYs (user terminals) will be unusable. This function restarts them.
*
* This is only applicable/effective when inserting the module after any tty has started. So in our scenario pretty much
* only for debugging as during normal operation TTYs are started way later than this module.
*/
static void restart_ttys(void)
{
pr_loc_dbg("Restarting TTYs");

struct task_struct *proc;
for_each_process(proc) {
if (proc->parent->pid > 2 ||
(strcmp(proc->parent->comm, "init") != 0 && strcmp(proc->parent->comm, "kthreadd") != 0))
continue;

//@todo this will only work for not-loggedin consoles hmm
if(strcmp(proc->comm, "ash") == 0 || strcmp(proc->comm, "sh") == 0) {
pr_loc_dbg("Killing TTY process %s (%d)", proc->comm, proc->pid);
kill_pgrp(task_pid(proc), SIGKILL, 1);
}
}
}

/**
* Swaps first two serial ports & updates kernel console indexing
*
* Some kernels are compiled with CONFIG_SYNO_X86_SERIAL_PORT_SWAP set which effectively swaps first two serial ports.
* This function reverses that. It also makes sure to move kernel console output between them (if configured)
*
* Swapping the serials involves two things: swapping console drivers and swapping kernel console printk itself.
* The first one can be done on any kernel by modifying exported "console_drivers". The second one requires an access
* to either struct serial8250_ports (drivers/tty/serial/8250/8250_core.c) or to struct console_cmdline
* (kernel/printk/printk.c). Both of them are static so they're no-go.
* Kernels before v4.1 had a convenient method update_console_cmdline(). Unfortunately this method was removed:
* https://github.com/torvalds/linux/commit/c7cef0a84912cab3c9df8949b034e4aa62982ec9 so there's currently no method
* of un-swapping on v4 ;<
*
* Things we've tried:
* - Set the new console as preferred (making it default for /dev/console) -> /dev/ttyS0 and 1 are still wrong
* - Unregistering both ports and re-registering them -> we tried a bit and it's a nightmare to re-do and crashes the
* kernel
* - Unregistering and re-registering consoles
* - Recreating the flow for serial8250_isa_init_ports() -> it appears to work (i.e. doesn't crash) but the serial port
* is dead afterwards and doesn't pass any traffic
*
* Since there are no platforms running v4 with swapped serials (and hopefully there wouldn't be anymore) we're not
* digging any deeper into that. The current implementation is less-than-ideal on v3 as well as /dev/ttyS0 and 1 are
* still pointing to broken places... ehh, ffs why.
*
* Hours wasted trying to reverse stupid ports: 37 (increment when you think you've got it and you failed)
*/
static int swap_uarts(int from, int to)
{
int out = 0;
struct console *con;
struct console *ttyS0_con = NULL; //Populated by a console which was set to be on a *REAL* ttyS0 (ttyS1 swapped)
struct console *ttyS1_con = NULL; //Populated by a console which was set to be on a *REAL* ttyS1 (ttyS0 swapped)

console_lock(); //Stops (and buffers) printk calls while pulling console semaphore down
for_each_console(con) {
if (strcmp(con->name, CONSOLE_NAME_UART) != 0)
continue;

pr_loc_dbg("Swapping console %s%d index", con->name, con->index);
if (con->index == 1) {
con->index = 0;
ttyS1_con = con;
} else if (con->index == 0) {
con->index = 1;
ttyS0_con = con;
}
}
console_unlock(); //Flushes printks and releases semaphore

pr_loc_inf("Swapped %s0 & %s1, updating console %s%d => %s%d", CONSOLE_NAME_UART, CONSOLE_NAME_UART,
CONSOLE_NAME_UART, from, CONSOLE_NAME_UART, to);

//This call is only successful when loading early (not in the shell) so it CAN fail
_update_console_cmdline(CONSOLE_NAME_UART, from, CONSOLE_NAME_UART, to, CONSOLE_BAUD_UART);
serial_swapped = true;
restart_ttys();

return 0;
}

/**
* On some platforms (e.g. 918+) the first serial port appears to not be functional as it's not initialized properly.
*
* It is speculated that it has to do with "CONFIG_SYNO_X86_TTY_CONSOLE_OUTPUT=y" but it's not confirmed. If this is not
* fixed by this function setting kernel console output to ttyS0 will result in earlycon working as expected (as it
* doesn't use the normal 8250 driver) with nothing being transmitted as soon as earlycon is switched to the proper
* "console=" port.
*/
static int fix_muted_ttyS0(void)
{
int out = 0;
struct uart_port port = pc_com1_port;

if ((out = _early_serial_setup(&port)) != 0) {
pr_loc_err("Failed to register ttyS0 to hw port @ %lx", port.iobase);
return out;
}

pr_loc_dbg("Fixed muted ttyS0 to hw port @ %lx", port.iobase);
ttyS0_force_initted = true;
return out;
}

static int mute_ttyS0(void)
{
pr_loc_dbg("Re-muting ttyS0");
serial8250_unregister_port(0);

return 0;
}

int register_uart_fixer(const hw_config_uart_fixer *hw)
{
int out = 0;

if (
(hw->swap_serial && (out = swap_uarts(1, 0)) != 0) ||
(hw->reinit_ttyS0 && (out = fix_muted_ttyS0()) != 0)
) {
pr_loc_err("Failed to register UART fixer");

return out;
}

pr_loc_inf("UART fixer registered");
return out;
}

int unregister_uart_fixer(void)
{
int out = 0;

if (
(serial_swapped && (out = swap_uarts(0, 1)) != 0) ||
(ttyS0_force_initted && (out = mute_ttyS0()) != 0)
) {
pr_loc_err("Failed to unregister UART fixer");
return out;
}

pr_loc_inf("UART fixer unregistered");
return out;
}
8 changes: 8 additions & 0 deletions shim/uart_fixer.h
@@ -0,0 +1,8 @@
#ifndef REDPILL_UART_FIXER_H
#define REDPILL_UART_FIXER_H

typedef struct hw_config hw_config_uart_fixer;
int register_uart_fixer(const hw_config_uart_fixer *hw);
int unregister_uart_fixer(void);

#endif //REDPILL_UART_FIXER_H

3 comments on commit c390f74

@semool
Copy link

@semool semool commented on c390f74 Aug 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#include "../../config/runtime_config.h" //hw_config
#include "../../internal/call_protected.h" //early_serial_setup(), update_console_cmdline()

must be:

#include "../config/runtime_config.h" //hw_config
#include "../internal/call_protected.h" //early_serial_setup(), update_console_cmdline()

but it crash:

[    3.308799] kernel tried to execute NX-protected page - exploit attempt? (uid: 0)
[    3.309004] BUG: unable to handle kernel paging request at ffffffff81928292
[    3.309004] IP: [<ffffffff81928292>] early_serial_setup+0x0/0x150
[    3.309004] PGD 180d067 PUD 180e063 PMD 2768ad063 PTE 8000000001928163
[    3.309004] Oops: 0011 [#1] SMP
[    3.309004] Modules linked in: redpill(OE+)
[    3.309004] CPU: 3 PID: 3890 Comm: insmod Tainted: G           OE   4.4.180+ #41890
[    3.309004] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.14.0-0-g155821a1990b-prebuilt.qemu.org 04/01/2014
[    3.309004] task: ffff88026e3d4500 ti: ffff88007b2fc000 task.ti: ffff88007b2fc000
[    3.309004] RIP: 0010:[<ffffffff81928292>]  [<ffffffff81928292>] early_serial_setup+0x0/0x150
[    3.309004] RSP: 0018:ffff88007b2ffb38  EFLAGS: 00010246
[    3.309004] RAX: ffffffff81928292 RBX: ffff88007b2ffb58 RCX: 000000000000026c
[    3.309004] RDX: 0000000000000001 RSI: 0000000000000246 RDI: ffff88007b2ffb58
[    3.309004] RBP: ffff88007b2ffb48 R08: 6c6c69706465723c R09: 000000000000026c
[    3.309004] R10: ffffffff816fab40 R11: 65746365746f7270 R12: ffff88007b2b62e0
[    3.309004] R13: 0000000000000000 R14: ffffffffa000f000 R15: ffff8802748b5a80
[    3.309004] FS:  00007f3603bc5740(0000) GS:ffff88027fd80000(0000) knlGS:0000000000000000
[    3.309004] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[    3.309004] CR2: ffffffff81928292 CR3: 000000007b2e3000 CR4: 00000000000006f0
[    3.309004] Stack:
[    3.309004]  ffffffffa000107c 0000000000000000 ffff88007b2ffcd0 ffffffffa0005833
[    3.309004]  0000000000000000 00000000000003f8 0000000000000000 0000000000000000
[    3.309004]  0000000000000000 0000000000000000 0000000000000000 0000000000000000
[    3.309004] Call Trace:
[    3.309004]  [<ffffffffa000107c>] ? _early_serial_setup+0x1c/0xa0 [redpill]
[    3.309004]  [<ffffffffa0005833>] fix_muted_ttyS0+0x53/0xc0 [redpill]
[    3.309004]  [<ffffffffa00058eb>] register_uart_fixer+0x1b/0x90 [redpill]
[    3.309004]  [<ffffffffa000f07b>] init_redpill+0x7b/0x143 [redpill]
[    3.309004]  [<ffffffff810003b8>] do_one_initcall+0x88/0x1a0
[    3.309004]  [<ffffffff811305d3>] do_init_module+0x5a/0x1bf
[    3.309004]  [<ffffffff810cb1a0>] load_module+0x1ca0/0x22b0
[    3.309004]  [<ffffffff810c80b0>] ? __symbol_put+0x40/0x40
[    3.309004]  [<ffffffff810cb981>] SYSC_finit_module+0x81/0xa0
[    3.309004]  [<ffffffff810cb9b9>] SyS_finit_module+0x9/0x10
[    3.309004]  [<ffffffff8157998a>] entry_SYSCALL_64_fastpath+0x1e/0x8e
[    3.309004] Code: cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc <cc> cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc
[    3.309004] RIP  [<ffffffff81928292>] early_serial_setup+0x0/0x150
[    3.309004]  RSP <ffff88007b2ffb38>
[    3.309004] CR2: ffffffff81928292
[    3.309004] ---[ end trace 28aea1d924898e89 ]---
Killed```

@jumkey
Copy link
Contributor

@jumkey jumkey commented on c390f74 Aug 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ffffffff81928109 T early_serial_setup
I found this line on System.map of DSM7.0
should ffffffff81928292->ffffffff81928109?

@ttg-public
Copy link
Member Author

@ttg-public ttg-public commented on c390f74 Aug 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#include "../../config/runtime_config.h" //hw_config
#include "../../internal/call_protected.h" //early_serial_setup(), update_console_cmdline()

must be:

#include "../config/runtime_config.h" //hw_config
#include "../internal/call_protected.h" //early_serial_setup(), update_console_cmdline()

but it crash:
...
Whoops... how that compiled on our end? We hit a happy path with folder structure probably ;)
As for the crash we were afraid it's gonna happen eventually. That trick works for all v6 versions but it doesn't for v7 unfortunately.

ffffffff81928109 T early_serial_setup
I found this line on System.map of DSM7.0
should ffffffff81928292->ffffffff81928109?
The address of the function is not the problem sadly. We get the function address from kallsyms (which is more or less contained in System.map) but the issue is some function in the kernel are valid only for some time. In this case early_serial_setup is valid only until the init is done as it's marked with __init. After that the kernel is free to remove it from memory and mark that region as NX... and it does that in newer kernels (but not in older ones).
We will find a new way I guess ;)

Issue for the path: #9
Issue for the serial setup: #10

p.s. GitHub comments for commits don't trigger any notifications so we missed that initially ;)

Please sign in to comment.