├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── common.h ├── compat ├── string_compat.c ├── string_compat.h └── toolkit │ ├── drivers │ └── usb │ │ └── storage │ │ └── usb.h │ ├── fs │ └── proc │ │ └── internal.h │ └── include │ └── .gitkeep ├── config ├── cmdline_delegate.c ├── cmdline_delegate.h ├── cmdline_opts.h ├── platform_types.h ├── platforms.h ├── runtime_config.c ├── runtime_config.h ├── uart_defs.h └── vpci_types.h ├── debug ├── debug_execve.c ├── debug_execve.h └── debug_vuart.h ├── internal ├── call_protected.c ├── call_protected.h ├── helper │ ├── math_helper.c │ ├── math_helper.h │ ├── memory_helper.c │ ├── memory_helper.h │ ├── symbol_helper.c │ └── symbol_helper.h ├── intercept_driver_register.c ├── intercept_driver_register.h ├── intercept_execve.c ├── intercept_execve.h ├── ioscheduler_fixer.c ├── ioscheduler_fixer.h ├── notifier_base.h ├── override │ ├── override_symbol.c │ ├── override_symbol.h │ ├── override_syscall.c │ └── override_syscall.h ├── scsi │ ├── hdparam.h │ ├── scsi_notifier.c │ ├── scsi_notifier.h │ ├── scsi_notifier_list.c │ ├── scsi_notifier_list.h │ ├── scsi_toolbox.c │ ├── scsi_toolbox.h │ └── scsiparam.h ├── stealth.c ├── stealth.h ├── stealth │ ├── sanitize_cmdline.c │ └── sanitize_cmdline.h ├── uart │ ├── uart_swapper.c │ ├── uart_swapper.h │ ├── virtual_uart.c │ ├── virtual_uart.h │ ├── vuart_internal.h │ ├── vuart_virtual_irq.c │ └── vuart_virtual_irq.h ├── virtual_pci.c └── virtual_pci.h ├── redpill_main.c ├── redpill_main.h ├── shim ├── bios │ ├── bios_hwcap_shim.c │ ├── bios_hwcap_shim.h │ ├── bios_hwmon_shim.c │ ├── bios_hwmon_shim.h │ ├── bios_shims_collection.c │ ├── bios_shims_collection.h │ ├── mfgbios_types.h │ ├── rtc_proxy.c │ └── rtc_proxy.h ├── bios_shim.c ├── bios_shim.h ├── block_fw_update_shim.c ├── block_fw_update_shim.h ├── boot_dev │ ├── boot_shim_base.c │ ├── boot_shim_base.h │ ├── fake_sata_boot_shim.c │ ├── fake_sata_boot_shim.h │ ├── native_sata_boot_shim.c │ ├── native_sata_boot_shim.h │ ├── usb_boot_shim.c │ └── usb_boot_shim.h ├── boot_device_shim.c ├── boot_device_shim.h ├── disable_exectutables.c ├── disable_exectutables.h ├── pci_shim.c ├── pci_shim.h ├── pmu_shim.c ├── pmu_shim.h ├── shim_base.h ├── storage │ ├── sata_port_shim.c │ ├── sata_port_shim.h │ ├── smart_shim.c │ └── smart_shim.h ├── uart_fixer.c └── uart_fixer.h └── tools ├── README.md ├── always_serial.sh ├── always_telnet.sh ├── inject_rp_ko.sh └── make_all.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | email.patch 55 | linux-*/ 56 | .idea/ 57 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PWD := $(shell pwd) 2 | 3 | ifeq ($(LINUX_SRC),) 4 | LINUX_SRC := "$(PWD)/../linux-3.10.x-bromolow-25426" 5 | endif 6 | 7 | SRCS-$(DBG_EXECVE) += debug/debug_execve.c 8 | ccflags-$(DBG_EXECVE) += -DRPDBG_EXECVE 9 | SRCS-y += compat/string_compat.c \ 10 | \ 11 | internal/helper/math_helper.c internal/helper/memory_helper.c internal/helper/symbol_helper.c \ 12 | internal/scsi/scsi_toolbox.c internal/scsi/scsi_notifier_list.c internal/scsi/scsi_notifier.c \ 13 | internal/override/override_symbol.c internal/override/override_syscall.c internal/intercept_execve.c \ 14 | internal/call_protected.c internal/intercept_driver_register.c internal/stealth/sanitize_cmdline.c \ 15 | internal/stealth.c internal/virtual_pci.c internal/uart/uart_swapper.c internal/uart/vuart_virtual_irq.c \ 16 | internal/uart/virtual_uart.c internal/ioscheduler_fixer.c \ 17 | \ 18 | config/cmdline_delegate.c config/runtime_config.c \ 19 | \ 20 | shim/boot_dev/boot_shim_base.c shim/boot_dev/usb_boot_shim.c shim/boot_dev/fake_sata_boot_shim.c \ 21 | shim/boot_dev/native_sata_boot_shim.c shim/boot_device_shim.c \ 22 | \ 23 | shim/storage/smart_shim.c shim/storage/sata_port_shim.c \ 24 | shim/bios/bios_hwcap_shim.c shim/bios/bios_hwmon_shim.c shim/bios/rtc_proxy.c \ 25 | shim/bios/bios_shims_collection.c shim/bios_shim.c \ 26 | shim/block_fw_update_shim.c shim/disable_exectutables.c shim/pci_shim.c shim/pmu_shim.c shim/uart_fixer.c \ 27 | \ 28 | redpill_main.c 29 | OBJS = $(SRCS-y:.c=.o) 30 | #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 31 | # in object file. So here we say the module file(s) which will create .ko(s) is "redpill.o" and that other objects which 32 | # must be linked (redpill-objs variable) 33 | obj-m += redpill.o 34 | redpill-objs := $(OBJS) 35 | ccflags-y += -std=gnu99 -fgnu89-inline -Wno-declaration-after-statement 36 | ccflags-y += -I$(src)/compat/toolkit/include 37 | 38 | ifndef RP_VERSION_POSTFIX 39 | RP_VERSION_POSTFIX := $(shell git rev-parse --is-inside-work-tree 1>/dev/null 2>/dev/null && echo -n "git-" && git log -1 --pretty='%h' 2>/dev/null || date '+at-%Y_%m_%d-%H_%M_%S') 40 | endif 41 | ccflags-y += -DRP_VERSION_POSTFIX="\"$(RP_VERSION_POSTFIX)\"" 42 | 43 | # Optimization settings per-target. Since LKM makefiles are evaluated twice (first with the specified target and second 44 | # time with target "modules") we need to set the custom target variable during first parsing and based on that variable 45 | # set additional CC-flags when the makefile is parsed for the second time 46 | ifdef RP_MODULE_TARGET 47 | ccflags-dev = -g -fno-inline -DDEBUG 48 | ccflags-test = -O3 49 | ccflags-prod = -O3 50 | ccflags-y += -DRP_MODULE_TARGET_VER=${RP_MODULE_TARGET_VER} # this is assumed to be defined when target is specified 51 | 52 | $(info RP-TARGET SPECIFIED AS ${RP_MODULE_TARGET} v${RP_MODULE_TARGET_VER}) 53 | 54 | # stealth mode can always be overridden but there are sane per-target defaults (see above) 55 | ifneq ($(STEALTH_MODE),) 56 | $(info STEATLH MODE OVERRIDE: ${STEALTH_MODE}) 57 | ccflags-y += -DSTEALTH_MODE=$(STEALTH_MODE) 58 | else 59 | ccflags-dev += -DSTEALTH_MODE=1 60 | ccflags-test += -DSTEALTH_MODE=2 61 | ccflags-prod += -DSTEALTH_MODE=3 62 | endif 63 | 64 | ccflags-y += ${ccflags-${RP_MODULE_TARGET}} 65 | else 66 | # during the first read of the makefile we don't get the RP_MODULE_TARGET - if for some reason we didn't get it during 67 | # the actual build phase it should explode (and it will if an unknown GCC flag is specified). We cannot sue makefile 68 | # error here as we don't know if the file is parsed for the first time or the second time. Just Kbuild peculiarities ;) 69 | ccflags-y = --bogus-flag-which-should-not-be-called-NO_RP_MODULE_TARGER_SPECIFIED 70 | endif 71 | 72 | # this MUST be last after all other options to force GNU89 for the file being a workaround for GCC bug #275674 73 | # see internal/scsi/scsi_notifier_list.h for detailed explanation 74 | CFLAGS_scsi_notifier_list.o += -std=gnu89 75 | 76 | # do NOT move this target - make <3.80 doesn't have a way to specify default target and takes the first one found 77 | default_error: 78 | $(error You need to specify one of the following targets: dev-v6, dev-v7, test-v6, test-v7, prod-v6, prod-v7, clean) 79 | 80 | # All v6 targets 81 | dev-v6: # kernel running in v6.2+ OS, all symbols included, debug messages included 82 | $(MAKE) -C $(LINUX_SRC) M=$(PWD) RP_MODULE_TARGET="dev" RP_MODULE_TARGET_VER="6" modules 83 | test-v6: # kernel running in v6.2+ OS, fully stripped with only warning & above (no debugs or info) 84 | $(MAKE) -C $(LINUX_SRC) M=$(PWD) RP_MODULE_TARGET="test" RP_MODULE_TARGET_VER="6" modules 85 | prod-v6: # kernel running in v6.2+ OS, fully stripped with no debug messages 86 | $(MAKE) -C $(LINUX_SRC) M=$(PWD) RP_MODULE_TARGET="prod" RP_MODULE_TARGET_VER="6" modules 87 | 88 | # All v7 targets 89 | dev-v7: # kernel running in v6.2+ OS, all symbols included, debug messages included 90 | $(MAKE) -C $(LINUX_SRC) M=$(PWD) RP_MODULE_TARGET="dev" RP_MODULE_TARGET_VER="7" modules 91 | test-v7: # kernel running in v6.2+ OS, fully stripped with only warning & above (no debugs or info) 92 | $(MAKE) -C $(LINUX_SRC) M=$(PWD) RP_MODULE_TARGET="test" RP_MODULE_TARGET_VER="7" modules 93 | prod-v7: # kernel running in v6.2+ OS, fully stripped with no debug messages 94 | $(MAKE) -C $(LINUX_SRC) M=$(PWD) RP_MODULE_TARGET="prod" RP_MODULE_TARGET_VER="7" modules 95 | 96 | clean: 97 | $(MAKE) -C $(LINUX_SRC) M=$(PWD) clean 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 💊 RedPill LKM 2 | 3 | --- 4 | 5 | ## THIS IS WORK IN PROGRESS 6 | There's nothing to run/see here (yet ;)). 7 | 8 | --- 9 | 10 | ## What is this? 11 | This is a major part of a tool which will be able to run a DSM instance for research purposes without 12 | engaging your real DS machine and risking your data in the process (ask me how I know...). 13 | 14 | ## Target audience 15 | This repository is target towards **developers** willing to learn and help with implementation of peculiarities of 16 | Synology's DSM Linux distribution. 17 | 18 | Read about the quirk in a separate repo: https://github.com/RedPill-TTG/dsm-research/tree/master/quirks 19 | 20 | ## How to build with Linux sources? 21 | 1. You need Synology's GPL sources for the kernel. Check the [Makefile](Makefile) for details 22 | 2. `cd` to kernel sources 23 | 3. Depending on the version: 24 | - **Linux v3** 25 | - `cp synoconfigs/bromolow .config` 26 | - **Linux v4** 27 | - `cp synoconfigs/apollolake .config` 28 | - `echo '+' > .scmversion` (otherwise it will error-out loading modules) 29 | 4. `make oldconfig ; make modules_prepare` 30 | 5. `cd` back to the module directory 31 | 6. `make LINUX_SRC=....` (path to linux sources, default: `../linux-3.10.x-bromolow-25426`) 32 | 7. You will get a `redpill.ko` module as the result, you can `insmod` it 33 | 34 | 35 | ## How to build with syno toolkit? 36 | The procedure to build with the toolkit is **not recommended**. However, some versions lack the kernel sources 37 | (e.g. v7 now) and thus can only use this method. 38 | 39 | 1. Get the appropriate toolkit from the [official SF repo](https://sourceforge.net/projects/dsgpl/files/toolkit/) 40 | - You want to get the `.dev.txz` file for the corresponding platform (e.g. `ds.bromolow-7.0.dev.txz`) 41 | - You only need to unpack a part of it: `tar -xvf ds.bromolow-7.0.dev.txz usr/local/x86_64-pc-linux-gnu/x86_64-pc-linux-gnu/sys-root/usr/lib/modules/DSM-7.0/build` 42 | - If the path above changed you can use `tar -tvf ds.bromolow-7.0.dev.txz | grep kfifo.h` to find the correct one 43 | 2. `cd` to the module directory 44 | 3. `make LINUX_SRC=/usr/local/x86_64-pc-linux-gnu/x86_64-pc-linux-gnu/sys-root/usr/lib/modules/DSM-7.0/build` 45 | 4. You will get a `redpill.ko` module as the result, you can `insmod` it 46 | 47 | 48 | ## Additional make options 49 | While calling `make` you can also add these additional modifiers (e.g. `make FOO BAR`): 50 | - `DBG_EXECVE=y`: enabled debugging of every `execve()` call with arguments 51 | - `STEALTH_MODE=#`: controls the level of "stealthiness", see `STEALTH_MODE_*` in `internal/stealth.h`; it's 52 | `STEALTH_MODE_BASIC` by default 53 | - `LINUX_SRC=...`: path to the linux kernel sources (`./linux-3.10.x-bromolow-25426` by default) 54 | 55 | On Debian-based systems you will need `build-essential` and `libssl-dev` packages at minimum. 56 | 57 | ## Documentation split 58 | The documentation regarding actual quirks/mechanisms/discoveries regarding DSM is present in a dedicated research repo 59 | at https://github.com/RedPill-TTG/dsm-research/. Documentation in this repository is solely aimed to explain 60 | implementation details of the kernel module. It will mostly be available in forms of long(ish) doc blocks. 61 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILLLKM_COMMON_H 2 | #define REDPILLLKM_COMMON_H 3 | 4 | /******************************************** Available whole-module flags ********************************************/ 5 | //This (shameful) flag disables shims which cannot be properly unloaded to make debugging of other things easier 6 | //#define DBG_DISABLE_UNLOADABLE 7 | 8 | //disabled uart unswapping even if needed (useful for hand-loading while running) 9 | //#define DBG_DISABLE_UART_SWAP_FIX 10 | 11 | //Whether to cause a kernel panic when module fails to load internally (which should be normally done on production) 12 | #define KP_ON_LOAD_ERROR 13 | 14 | //Print A LOT of vUART debug messages 15 | //#define VUART_DEBUG_LOG 16 | 17 | //Enabled printing of all ioctl() calls (hooked or not) 18 | //#define DBG_SMART_PRINT_ALL_IOCTL 19 | 20 | //Normally GetHwCapability calls (checking what hardware supports) are responded internally. Setting this DBG adds log 21 | // of all requests & responses for hardware capabilities (and there're frquent but not overwhelming). Additionally this 22 | // option turns on additional calls to the original GetHwCapability and logs compared values. Some values are ALWAYS 23 | // proxied to the original GetHwCapability 24 | //#define DBG_HWCAP 25 | 26 | //Debug all hardware monitoring features (shim/bios/bios_hwmon_shim.c) 27 | //#define DBG_HWMON 28 | /**********************************************************************************************************************/ 29 | 30 | #include "internal/stealth.h" 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include //kmalloc 36 | #include 37 | #include "compat/string_compat.h" 38 | #include //bool & others 39 | 40 | /************************************************** Strings handling **************************************************/ 41 | #define get_static_name(variable) #variable 42 | #define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) 43 | #define strlen_static(param) (sizeof(param)-1) //gets the size of a string minus trailing nullbyte (useful for partial matches) 44 | #define strlen_to_size(len) (sizeof(char) * ((len)+1)) //useful for static strings, use strsize() for dynamic ones 45 | #define strsize(param) strlen_to_size(strlen(param)) //strlen including NULLbyte; useful for kmalloc-ing 46 | /**********************************************************************************************************************/ 47 | 48 | /****************************************** Dynamic memory allocation helpers *****************************************/ 49 | //[internal] Cleans up & provides standard reporting and return 50 | #define __kalloc_err_report_clean(variable, size, exit) \ 51 | (variable) = NULL; \ 52 | pr_loc_crt("kernel memory alloc failure - tried to allocate %ld bytes for %s", \ 53 | (long)(size), get_static_name(variable)); \ 54 | return exit; 55 | 56 | //Use these if you need to do a manual malloc with some extra checks but want to return a consistant message 57 | #define kalloc_error_int(variable, size) do { __kalloc_err_report_clean(variable, size, -ENOMEM); } while(0) 58 | #define kalloc_error_ptr(variable, size) do { __kalloc_err_report_clean(variable, size, ERR_PTR(-ENOMEM)); } while(0) 59 | 60 | //[internal] Reserves memory & checks result 61 | #define __kalloc_or_exit(type, variable, size, exit_type) \ 62 | (variable) = (type)(size, GFP_KERNEL); \ 63 | if (unlikely(!(variable))) { kalloc_error_ ## exit_type (variable, size); } 64 | 65 | //Use these to do a standard malloc with error reporting 66 | #define kmalloc_or_exit_int(variable, size) do { __kalloc_or_exit(kmalloc, variable, size, int); } while(0) 67 | #define kmalloc_or_exit_ptr(variable, size) do { __kalloc_or_exit(kmalloc, variable, size, ptr); } while(0) 68 | #define kzalloc_or_exit_int(variable, size) do { __kalloc_or_exit(kzalloc, variable, size, int); } while(0) 69 | #define kzalloc_or_exit_ptr(variable, size) do { __kalloc_or_exit(kzalloc, variable, size, ptr); } while(0) 70 | #define try_kfree(variable) do { if(variable) { kfree(variable); } } while(0) 71 | /**********************************************************************************************************************/ 72 | 73 | /****************************************************** Logging *******************************************************/ 74 | #define _pr_loc_crt(fmt, ...) pr_crit( "<%s/%s:%d> " pr_fmt(fmt) "\n", KBUILD_MODNAME, __FILENAME__, __LINE__, ##__VA_ARGS__) 75 | #define _pr_loc_err(fmt, ...) pr_err ( "<%s/%s:%d> " pr_fmt(fmt) "\n", KBUILD_MODNAME, __FILENAME__, __LINE__, ##__VA_ARGS__) 76 | #define _pr_loc_wrn(fmt, ...) pr_warn( "<%s/%s:%d> " pr_fmt(fmt) "\n", KBUILD_MODNAME, __FILENAME__, __LINE__, ##__VA_ARGS__) 77 | #define _pr_loc_inf(fmt, ...) pr_info( "<%s/%s:%d> " pr_fmt(fmt) "\n", KBUILD_MODNAME, __FILENAME__, __LINE__, ##__VA_ARGS__) 78 | #define _pr_loc_dbg(fmt, ...) pr_info( "<%s/%s:%d> " pr_fmt(fmt) "\n", KBUILD_MODNAME, __FILENAME__, __LINE__, ##__VA_ARGS__) 79 | #define _pr_loc_dbg_raw(fmt, ...) printk(fmt, ##__VA_ARGS__) 80 | #define _pr_loc_bug(fmt, ...) \ 81 | do { \ 82 | pr_err("<%s/%s:%d> !!BUG!! " pr_fmt(fmt) "\n", KBUILD_MODNAME, __FILENAME__, __LINE__, ##__VA_ARGS__); \ 83 | WARN(1, "BUG log triggered"); \ 84 | } while(0) 85 | 86 | #if STEALTH_MODE >= STEALTH_MODE_FULL //all logs will be disabled in full 87 | #define pr_loc_crt(fmt, ...) 88 | #define pr_loc_err(fmt, ...) 89 | #define pr_loc_wrn(fmt, ...) 90 | #define pr_loc_inf(fmt, ...) 91 | #define pr_loc_dbg(fmt, ...) 92 | #define pr_loc_dbg_raw(fmt, ...) 93 | #define pr_loc_bug(fmt, ...) 94 | #define DBG_ALLOW_UNUSED(var) ((void)var) //in debug modes some variables are seen as unused (as they're only for dbg) 95 | 96 | #elif STEALTH_MODE >= STEALTH_MODE_NORMAL //in normal mode we only warnings/errors/etc. 97 | #define pr_loc_crt _pr_loc_crt 98 | #define pr_loc_err _pr_loc_err 99 | #define pr_loc_wrn _pr_loc_wrn 100 | #define pr_loc_inf(fmt, ...) 101 | #define pr_loc_dbg(fmt, ...) 102 | #define pr_loc_dbg_raw(fmt, ...) 103 | #define pr_loc_bug _pr_loc_bug 104 | #define DBG_ALLOW_UNUSED(var) ((void)var) //in debug modes some variables are seen as unused (as they're only for dbg) 105 | 106 | #else 107 | #define pr_loc_crt _pr_loc_crt 108 | #define pr_loc_err _pr_loc_err 109 | #define pr_loc_inf _pr_loc_inf 110 | #define pr_loc_wrn _pr_loc_wrn 111 | #define pr_loc_dbg _pr_loc_dbg 112 | #define pr_loc_dbg_raw _pr_loc_dbg_raw 113 | #define pr_loc_bug _pr_loc_bug 114 | #define DBG_ALLOW_UNUSED(var) //when debug logs are enables we don't silence unused variables warnings 115 | 116 | #endif //STEALTH_MODE 117 | /**********************************************************************************************************************/ 118 | 119 | #ifndef RP_MODULE_TARGET_VER 120 | #error "The RP_MODULE_TARGET_VER is not defined - it is required to properly set VTKs" 121 | #endif 122 | 123 | //Before you change that you need to go and check all usages of RP_MODULE_TARGET_VER 124 | #if RP_MODULE_TARGET_VER != 6 && RP_MODULE_TARGET_VER != 7 125 | #error "The RP_MODULE_TARGET_VER value is invalid" 126 | #endif 127 | 128 | #endif //REDPILLLKM_COMMON_H 129 | -------------------------------------------------------------------------------- /compat/string_compat.c: -------------------------------------------------------------------------------- 1 | /* 2 | * linux/lib/string.c 3 | * Modified to take care of kernel versions 4 | * 5 | * Copyright (C) 1991, 1992 Linus Torvalds 6 | */ 7 | #include "string_compat.h" 8 | #include //E2BIG 9 | 10 | #if LINUX_VERSION_CODE <= KERNEL_VERSION(4,3,0) 11 | #include 12 | #include 13 | #include //PAGE_SIZE 14 | 15 | /** 16 | * strscpy - Copy a C-string into a sized buffer 17 | * @dest: Where to copy the string to 18 | * @src: Where to copy the string from 19 | * @count: Size of destination buffer 20 | * 21 | * Copy the string, or as much of it as fits, into the dest buffer. 22 | * The routine returns the number of characters copied (not including 23 | * the trailing NUL) or -E2BIG if the destination buffer wasn't big enough. 24 | * The behavior is undefined if the string buffers overlap. 25 | * The destination buffer is always NUL terminated, unless it's zero-sized. 26 | * 27 | * Preferred to strlcpy() since the API doesn't require reading memory 28 | * from the src string beyond the specified "count" bytes, and since 29 | * the return value is easier to error-check than strlcpy()'s. 30 | * In addition, the implementation is robust to the string changing out 31 | * from underneath it, unlike the current strlcpy() implementation. 32 | * 33 | * Preferred to strncpy() since it always returns a valid string, and 34 | * doesn't unnecessarily force the tail of the destination buffer to be 35 | * zeroed. If the zeroing is desired, it's likely cleaner to use strscpy() 36 | * with an overflow test, then just memset() the tail of the dest buffer. 37 | */ 38 | ssize_t strscpy(char *dest, const char *src, size_t count) 39 | { 40 | const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS; 41 | size_t max = count; 42 | long res = 0; 43 | 44 | if (count == 0) 45 | return -E2BIG; 46 | 47 | #ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS 48 | /* 49 | * If src is unaligned, don't cross a page boundary, 50 | * since we don't know if the next page is mapped. 51 | */ 52 | if ((long)src & (sizeof(long) - 1)) { 53 | size_t limit = PAGE_SIZE - ((long)src & (PAGE_SIZE - 1)); 54 | if (limit < max) 55 | max = limit; 56 | } 57 | #else 58 | /* If src or dest is unaligned, don't do word-at-a-time. */ 59 | if (((long) dest | (long) src) & (sizeof(long) - 1)) 60 | max = 0; 61 | #endif 62 | 63 | while (max >= sizeof(unsigned long)) { 64 | unsigned long c, data; 65 | 66 | c = *(unsigned long *)(src+res); 67 | *(unsigned long *)(dest+res) = c; 68 | if (has_zero(c, &data, &constants)) { 69 | data = prep_zero_mask(c, data, &constants); 70 | data = create_zero_mask(data); 71 | return res + find_zero(data); 72 | } 73 | res += sizeof(unsigned long); 74 | count -= sizeof(unsigned long); 75 | max -= sizeof(unsigned long); 76 | } 77 | 78 | while (count) { 79 | char c; 80 | 81 | c = src[res]; 82 | dest[res] = c; 83 | if (!c) 84 | return res; 85 | res++; 86 | count--; 87 | } 88 | 89 | /* Hit buffer length without finding a NUL; force NUL-termination. */ 90 | if (res) 91 | dest[res-1] = '\0'; 92 | 93 | return -E2BIG; 94 | } 95 | #endif //kernel 4.3.0 -------------------------------------------------------------------------------- /compat/string_compat.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_STRING_COMPAT_H 2 | #define REDPILL_STRING_COMPAT_H 3 | 4 | #include //KERNEL_VERSION() 5 | #include //ssize_t 6 | 7 | #if LINUX_VERSION_CODE <= KERNEL_VERSION(4,3,0) 8 | ssize_t __must_check strscpy(char *, const char *, size_t); 9 | #endif 10 | 11 | #endif //REDPILL_STRING_COMPAT_H 12 | -------------------------------------------------------------------------------- /compat/toolkit/drivers/usb/storage/usb.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Cherry-picked USB.h internal structures from Linux v4.4.x. If possible avoid using anything from this file like fire. 3 | * 4 | * ORIGINAL FILE HEADER PRESERVED BELOW 5 | * ------------------------------------ 6 | * Driver for USB Mass Storage compliant devices 7 | * Main Header File 8 | * 9 | * Current development and maintenance by: 10 | * (c) 1999-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net) 11 | * 12 | * Initial work by: 13 | * (c) 1999 Michael Gee (michael@linuxspecific.com) 14 | * 15 | * This driver is based on the 'USB Mass Storage Class' document. This 16 | * describes in detail the protocol used to communicate with such 17 | * devices. Clearly, the designers had SCSI and ATAPI commands in 18 | * mind when they created this document. The commands are all very 19 | * similar to commands in the SCSI-II and ATAPI specifications. 20 | * 21 | * It is important to note that in a number of cases this class 22 | * exhibits class-specific exemptions from the USB specification. 23 | * Notably the usage of NAK, STALL and ACK differs from the norm, in 24 | * that they are used to communicate wait, failed and OK on commands. 25 | * 26 | * Also, for certain devices, the interrupt endpoint is used to convey 27 | * status of a command. 28 | * 29 | * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more 30 | * information about this driver. 31 | * 32 | * This program is free software; you can redistribute it and/or modify it 33 | * under the terms of the GNU General Public License as published by the 34 | * Free Software Foundation; either version 2, or (at your option) any 35 | * later version. 36 | * 37 | * This program is distributed in the hope that it will be useful, but 38 | * WITHOUT ANY WARRANTY; without even the implied warranty of 39 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 40 | * General Public License for more details. 41 | * 42 | * You should have received a copy of the GNU General Public License along 43 | * with this program; if not, write to the Free Software Foundation, Inc., 44 | * 675 Mass Ave, Cambridge, MA 02139, USA. 45 | */ 46 | 47 | #ifndef REDPILL_USB_H 48 | #define REDPILL_USB_H 49 | 50 | #warning "Using compatibility file for drivers/usb/storage/usb.h - if possible do NOT compile using toolkit" 51 | 52 | //This structure didn't change substantially since v2.6 days; 5.14 is simply the newest one we checked - it will 53 | // probably remain unchanged for years to come 54 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,0) && LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0) //v3.3 - v5.14 55 | #include //struct usb_sg_request 56 | 57 | struct us_data; 58 | typedef int (*trans_cmnd)(struct scsi_cmnd *, struct us_data*); 59 | typedef int (*trans_reset)(struct us_data*); 60 | typedef void (*proto_cmnd)(struct scsi_cmnd*, struct us_data*); 61 | typedef void (*extra_data_destructor)(void *); /* extra data destructor */ 62 | typedef void (*pm_hook)(struct us_data *, int); /* power management hook */ 63 | 64 | struct us_data { 65 | /* The device we're working with 66 | * It's important to note: 67 | * (o) you must hold dev_mutex to change pusb_dev 68 | */ 69 | struct mutex dev_mutex; /* protect pusb_dev */ 70 | struct usb_device *pusb_dev; /* this usb_device */ 71 | struct usb_interface *pusb_intf; /* this interface */ 72 | struct us_unusual_dev *unusual_dev; /* device-filter entry */ 73 | unsigned long fflags; /* fixed flags from filter */ 74 | unsigned long dflags; /* dynamic atomic bitflags */ 75 | unsigned int send_bulk_pipe; /* cached pipe values */ 76 | unsigned int recv_bulk_pipe; 77 | unsigned int send_ctrl_pipe; 78 | unsigned int recv_ctrl_pipe; 79 | unsigned int recv_intr_pipe; 80 | 81 | /* information about the device */ 82 | char *transport_name; 83 | char *protocol_name; 84 | __le32 bcs_signature; 85 | u8 subclass; 86 | u8 protocol; 87 | u8 max_lun; 88 | 89 | u8 ifnum; /* interface number */ 90 | u8 ep_bInterval; /* interrupt interval */ 91 | 92 | /* function pointers for this device */ 93 | trans_cmnd transport; /* transport function */ 94 | trans_reset transport_reset; /* transport device reset */ 95 | proto_cmnd proto_handler; /* protocol handler */ 96 | 97 | /* SCSI interfaces */ 98 | struct scsi_cmnd *srb; /* current srb */ 99 | unsigned int tag; /* current dCBWTag */ 100 | char scsi_name[32]; /* scsi_host name */ 101 | 102 | /* control and bulk communications data */ 103 | struct urb *current_urb; /* USB requests */ 104 | struct usb_ctrlrequest *cr; /* control requests */ 105 | struct usb_sg_request current_sg; /* scatter-gather req. */ 106 | unsigned char *iobuf; /* I/O buffer */ 107 | dma_addr_t iobuf_dma; /* buffer DMA addresses */ 108 | struct task_struct *ctl_thread; /* the control thread */ 109 | 110 | /* mutual exclusion and synchronization structures */ 111 | struct completion cmnd_ready; /* to sleep thread on */ 112 | struct completion notify; /* thread begin/end */ 113 | wait_queue_head_t delay_wait; /* wait during reset */ 114 | struct delayed_work scan_dwork; /* for async scanning */ 115 | 116 | /* subdriver information */ 117 | void *extra; /* Any extra data */ 118 | extra_data_destructor extra_destructor;/* extra data destructor */ 119 | #ifdef CONFIG_PM 120 | pm_hook suspend_resume_hook; 121 | #endif 122 | 123 | /* hacks for READ CAPACITY bug handling */ 124 | int use_last_sector_hacks; 125 | int last_sector_retries; 126 | }; 127 | #endif //LINUX_VERSION_CODE check 128 | 129 | 130 | struct Scsi_Host; 131 | static inline struct us_data *host_to_us(struct Scsi_Host *host) { 132 | return (struct us_data *) host->hostdata; 133 | } 134 | 135 | #endif //REDPILL_USB_H -------------------------------------------------------------------------------- /compat/toolkit/fs/proc/internal.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ 2 | /* Internal procfs definitions 3 | * 4 | * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. 5 | * Written by David Howells (dhowells@redhat.com) 6 | * 7 | * This file contains curated definitions from original fs/proc/internal.h file which we (unfortunately) need to access. 8 | * As the internal.h, as the name implies, is meant for in-tree use only official toolkits lack it. 9 | * Kernel version constrains here are taken from the real kernel source tree to prevent including wrong structs 10 | * definitions. Please keep it neat as mismatch will cause very hard to debug problems. 11 | */ 12 | 13 | #warning "Using compatibility file for fs/proc/internal.h - if possible do NOT compile using toolkit" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) && LINUX_VERSION_CODE < KERNEL_VERSION(3,19,0) //v3.10 - v3.18 21 | struct proc_dir_entry { 22 | unsigned int low_ino; 23 | umode_t mode; 24 | nlink_t nlink; 25 | kuid_t uid; 26 | kgid_t gid; 27 | loff_t size; 28 | const struct inode_operations *proc_iops; 29 | const struct file_operations *proc_fops; 30 | struct proc_dir_entry *next, *parent, *subdir; 31 | void *data; 32 | atomic_t count; /* use count */ 33 | atomic_t in_use; /* number of callers into module in progress; */ 34 | /* negative -> it's going away RSN */ 35 | struct completion *pde_unload_completion; 36 | struct list_head pde_openers; /* who did ->open, but not ->release */ 37 | spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */ 38 | u8 namelen; 39 | char name[]; 40 | }; 41 | 42 | union proc_op { 43 | int (*proc_get_link)(struct dentry *, struct path *); 44 | int (*proc_read)(struct task_struct *task, char *page); 45 | int (*proc_show)(struct seq_file *m, 46 | struct pid_namespace *ns, struct pid *pid, 47 | struct task_struct *task); 48 | }; 49 | 50 | struct proc_inode { 51 | struct pid *pid; 52 | int fd; 53 | union proc_op op; 54 | struct proc_dir_entry *pde; 55 | struct ctl_table_header *sysctl; 56 | struct ctl_table *sysctl_entry; 57 | struct proc_ns ns; 58 | struct inode vfs_inode; 59 | }; 60 | 61 | //See https://github.com/torvalds/linux/commit/771187d61bb3cbaf62c492ec3b8b789933f7691e 62 | //v3.19 - it's going away RSN */ 80 | struct completion *pde_unload_completion; 81 | struct list_head pde_openers; /* who did ->open, but not ->release */ 82 | spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */ 83 | u8 namelen; 84 | char name[]; 85 | }; 86 | 87 | union proc_op { 88 | int (*proc_get_link)(struct dentry *, struct path *); 89 | int (*proc_show)(struct seq_file *m, 90 | struct pid_namespace *ns, struct pid *pid, 91 | struct task_struct *task); 92 | }; 93 | 94 | struct proc_inode { 95 | struct pid *pid; 96 | int fd; 97 | union proc_op op; 98 | struct proc_dir_entry *pde; 99 | struct ctl_table_header *sysctl; 100 | struct ctl_table *sysctl_entry; 101 | const struct proc_ns_operations *ns_ops; 102 | struct inode vfs_inode; 103 | }; 104 | #endif 105 | 106 | //These methods are the same forever 107 | static inline struct proc_inode *PROC_I(const struct inode *inode) 108 | { 109 | return container_of(inode, struct proc_inode, vfs_inode); 110 | } 111 | 112 | static inline struct proc_dir_entry *PDE(const struct inode *inode) 113 | { 114 | return PROC_I(inode)->pde; 115 | } -------------------------------------------------------------------------------- /compat/toolkit/include/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedPill-TTG/redpill-lkm/0df1ec86ab5fc48749d700ee336671d4bee9e085/compat/toolkit/include/.gitkeep -------------------------------------------------------------------------------- /config/cmdline_delegate.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILLLKM_CMDLINE_DELEGATE_H 2 | #define REDPILLLKM_CMDLINE_DELEGATE_H 3 | 4 | #include "runtime_config.h" 5 | #include "cmdline_opts.h" 6 | 7 | /** 8 | * Provides an easy access to kernel cmdline 9 | * 10 | * Internally in the kernel code it is available as "saved_command_line". However that variable is not accessible for 11 | * modules. This function populates a char buffer with the cmdline extracted using other methods. 12 | * 13 | * WARNING: if something (e.g. sanitize cmdline) overrides the cmdline this method will return the overridden one! 14 | * However, this method caches the cmdline, so if you call it once it will cache the original one internally. 15 | * 16 | * @param cmdline_out A pointer to your buffer to save the cmdline 17 | * @param maxlen Your buffer space (in general you should use CMDLINE_MAX) 18 | * @return cmdline length on success or -E on error 19 | */ 20 | long get_kernel_cmdline(char *cmdline_out, unsigned long maxlen); 21 | 22 | /** 23 | * Extracts & processes parameters from kernel cmdline 24 | * 25 | * Note: it's not guaranteed that the config will be valid. Check runtime_config.h. 26 | * 27 | * @param config pointer to save configuration 28 | */ 29 | int extract_config_from_cmdline(struct runtime_config *config); 30 | 31 | #endif //REDPILLLKM_CMDLINE_DELEGATE_H 32 | -------------------------------------------------------------------------------- /config/cmdline_opts.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_CMDLINE_OPTS_H 2 | #define REDPILL_CMDLINE_OPTS_H 3 | 4 | #define CMDLINE_MAX 1024 //Max length of cmdline expected/processed; if longer a warning will be emitted 5 | #define CMDLINE_SEP "\t\n " 6 | 7 | /** 8 | * Kernel command line tokens. For clarity keep them separated. 9 | * CT = custom token 10 | * KT = kernel token (default or syno) 11 | * 12 | * All should be defined in the .h to allow accessing outside for hints in errors. 13 | */ 14 | #define CMDLINE_CT_VID "vid=" //Boot media Vendor ID override 15 | #define CMDLINE_CT_PID "pid=" //Boot media Product ID override 16 | #define CMDLINE_CT_MFG "mfg" //VID & PID override will use force-reinstall VID/PID combo 17 | #define CMDLINE_CT_MFG "mfg" //VID & PID override will use force-reinstall VID/PID combo 18 | #define CMDLINE_CT_DOM_SZMAX "dom_szmax=" //Max size of SATA device (MiB) to be considered a DOM (usually you should NOT use this) 19 | 20 | //Standard Linux cmdline tokens 21 | #define CMDLINE_KT_ELEVATOR "elevator=" //Sets I/O scheduler (we use it to load RP LKM earlier than normally possible) 22 | #define CMDLINE_KT_LOGLEVEL "loglevel=" 23 | #define CMDLINE_KT_PK_BUFFER "log_buf_len=" //Length of the printk ring buffer (should usually be increased for debug) 24 | #define CMDLINE_KT_EARLY_PK "earlyprintk" 25 | 26 | //Syno-specific cmdline tokens 27 | #define CMDLINE_KT_HW "syno_hw_version=" 28 | #define CMDLINE_KT_THAW "syno_port_thaw=" //?? 29 | 30 | //0|1 - whether to use native SATA Disk-on-Module for boot drive (syno); 2 - use fake/emulated SATA DOM (rp) 31 | #define CMDLINE_KT_SATADOM "synoboot_satadom=" 32 | # define CMDLINE_KT_SATADOM_DISABLED '0' 33 | # define CMDLINE_KT_SATADOM_NATIVE '1' 34 | # define CMDLINE_KT_SATADOM_FAKE '2' 35 | 36 | #define CMDLINE_KT_SN "sn=" 37 | #define CMDLINE_KT_NETIF_NUM "netif_num=" 38 | #define CMDLINE_KT_MACS "macs=" 39 | //You CANNOT simply add more macN= - DSM kernel only uses 4. If they ever support >4 you need to modify cmdline handling 40 | #define CMDLINE_KT_MAC1 "mac1=" 41 | #define CMDLINE_KT_MAC2 "mac2=" 42 | #define CMDLINE_KT_MAC3 "mac3=" 43 | #define CMDLINE_KT_MAC4 "mac4=" 44 | 45 | #endif //REDPILL_CMDLINE_OPTS_H 46 | -------------------------------------------------------------------------------- /config/platform_types.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_PLATFORM_TYPES_H 2 | #define REDPILL_PLATFORM_TYPES_H 3 | 4 | #include "vpci_types.h" //vpci_device_stub, MAX_VPCI_DEVS 5 | 6 | #ifndef RP_MODULE_TARGET_VER 7 | #error "The RP_MODULE_TARGET_VER is not defined - it is required to properly set VTKs" 8 | #endif 9 | 10 | //All HWMON_SYS enums defined here are for internal RP use only. Normally these have long names but duplicating names 11 | // across multiple platforms is wasteful (and causes platforms.h compilation unit to grow) 12 | //While adding new constants here MAKE SURE TO NOT CONFLICT with existing ones defining names in synobios.h (here we 13 | // postfixed everything with _ID) 14 | enum hwmon_sys_thermal_zone_id { 15 | //i.e. "non-existent zone" so that we don't need another flag/number to indicated # of supported zones 16 | HWMON_SYS_TZONE_NULL_ID = 0, 17 | HWMON_SYS_TZONE_REMOTE1_ID, 18 | HWMON_SYS_TZONE_REMOTE2_ID, 19 | HWMON_SYS_TZONE_LOCAL_ID, 20 | HWMON_SYS_TZONE_SYSTEM_ID, 21 | HWMON_SYS_TZONE_ADT1_LOC_ID, 22 | HWMON_SYS_TZONE_ADT2_LOC_ID, 23 | }; 24 | #define HWMON_SYS_THERMAL_ZONE_IDS 5 //number of thermal zones minus the fake NULL_ID 25 | 26 | enum hwmon_sys_voltage_sensor_id { 27 | //i.e. "non-existent sensor type" so that we don't need another flag/number to indicated # of supported ones 28 | HWMON_SYS_VSENS_NULL_ID = 0, 29 | HWMON_SYS_VSENS_VCC_ID, 30 | HWMON_SYS_VSENS_VPP_ID, 31 | HWMON_SYS_VSENS_V33_ID, 32 | HWMON_SYS_VSENS_V5_ID, 33 | HWMON_SYS_VSENS_V12_ID, 34 | HWMON_SYS_VSENS_ADT1_V33_ID, 35 | HWMON_SYS_VSENS_ADT2_V33_ID, 36 | }; 37 | #define HWMON_SYS_VOLTAGE_SENSOR_IDS 7 //number of voltage sensors minus the fake NULL_ID 38 | 39 | enum hwmon_sys_fan_rpm_id { 40 | //i.e. "non-existent fan" so that we don't need another flag/number to indicated # of supported fans 41 | HWMON_SYS_FAN_NULL_ID = 0, 42 | HWMON_SYS_FAN1_ID, 43 | HWMON_SYS_FAN2_ID, 44 | HWMON_SYS_FAN3_ID, 45 | HWMON_SYS_FAN4_ID, 46 | }; 47 | #define HWMON_SYS_FAN_RPM_IDS 4 48 | 49 | enum hwmon_sys_hdd_bp_id { 50 | //i.e. "non-existent backplane sensor" so that we don't need another flag/number to indicated # of supported ones 51 | HWMON_SYS_HDD_BP_NULL_ID = 0, 52 | HWMON_SYS_HDD_BP_DETECT_ID, 53 | HWMON_SYS_HDD_BP_ENABLE_ID, 54 | }; 55 | #define HWMON_SYS_HDD_BP_IDS 2 //number of HDD backplane sensors minus the fake NULL_ID 56 | 57 | enum hw_psu_sensor_id { 58 | //i.e. "non-existent PSU sensor" so that we don't need another flag/number to indicated # of supported ones 59 | HWMON_PSU_NULL_ID = 0, 60 | HWMON_PSU_PWR_IN_ID, 61 | HWMON_PSU_PWR_OUT_ID, 62 | #if RP_MODULE_TARGET_VER == 6 63 | HWMON_PSU_TEMP_ID, 64 | #elif RP_MODULE_TARGET_VER == 7 65 | HWMON_PSU_TEMP1_ID, 66 | HWMON_PSU_TEMP2_ID, 67 | HWMON_PSU_TEMP3_ID, 68 | HWMON_PSU_FAN_VOLT, 69 | #endif 70 | HWMON_PSU_FAN_RPM_ID, 71 | HWMON_PSU_STATUS_ID, 72 | }; 73 | #if RP_MODULE_TARGET_VER == 6 74 | #define HWMON_PSU_SENSOR_IDS 2 //number of power supply sensors minus the fake NULL_ID 75 | #elif RP_MODULE_TARGET_VER == 7 76 | #define HWMON_PSU_SENSOR_IDS 8 //number of power supply sensors minus the fake NULL_ID 77 | #else 78 | #error "Unknown RP_MODULE_TARGET_VER version specified" 79 | #endif 80 | 81 | enum hwmon_sys_current_id { 82 | //i.e. "non-existent current sensor" so that we don't need another flag/number to indicated # of supported ones 83 | HWMON_SYS_CURR_NULL_ID = 0, 84 | HWMON_SYS_CURR_ADC_ID, 85 | }; 86 | #define HWMON_SYS_CURRENT_IDS 1 //number of current sensors minus the fake NULL_ID 87 | 88 | struct hw_config { 89 | const char *name; //the longest so far is "RR36015xs+++" (12+1) 90 | 91 | const struct vpci_device_stub pci_stubs[MAX_VPCI_DEVS]; 92 | 93 | //All custom flags 94 | const bool emulate_rtc:1; 95 | const bool swap_serial:1; //Whether ttyS0 and ttyS1 are swapped (reverses CONFIG_SYNO_X86_SERIAL_PORT_SWAP) 96 | const bool reinit_ttyS0:1; //Should the ttyS0 be forcefully re-initialized after module loads 97 | const bool fix_disk_led_ctrl:1; //Disabled libata-scsi bespoke disk led control (which often crashes some v4 platforms) 98 | 99 | //See SYNO_HWMON_SUPPORT_ID in include/linux/synobios.h GPLed sources - it defines which ones are possible 100 | //These define which parts of ACPI HWMON should be emulated 101 | //For those with GetHwCapability() note enable DBG_HWCAP which will force bios_hwcap_shim to print original values. 102 | // Unless there's a good reason to diverge from the platform-defined values you should not. 103 | //Supported hwmon sensors; order of sensors within type IS IMPORTANT to be accurate with a real hardware. The number 104 | // of sensors is derived from the enums defining their types. Internally the absolute maximum number is determined 105 | // by MAX_SENSOR_NUM defined in include/linux/synobios.h 106 | const bool has_cpu_temp:1; //GetHwCapability(id = CAPABILITY_CPU_TEMP) 107 | const struct hw_config_hwmon { 108 | enum hwmon_sys_thermal_zone_id sys_thermal[HWMON_SYS_THERMAL_ZONE_IDS]; //GetHwCapability(id = CAPABILITY_THERMAL) 109 | enum hwmon_sys_voltage_sensor_id sys_voltage[HWMON_SYS_VOLTAGE_SENSOR_IDS]; 110 | enum hwmon_sys_fan_rpm_id sys_fan_speed_rpm[HWMON_SYS_FAN_RPM_IDS]; //GetHwCapability(id = CAPABILITY_FAN_RPM_RPT) 111 | enum hwmon_sys_hdd_bp_id hdd_backplane[HWMON_SYS_HDD_BP_IDS]; 112 | enum hw_psu_sensor_id psu_status[HWMON_PSU_SENSOR_IDS]; 113 | enum hwmon_sys_current_id sys_current[HWMON_SYS_CURRENT_IDS]; 114 | } hwmon; 115 | }; 116 | 117 | #define platform_has_hwmon_thermal(hw_config_ptr) ((hw_config_ptr)->hwmon.sys_thermal[0] != HWMON_SYS_TZONE_NULL_ID) 118 | #define platform_has_hwmon_voltage(hw_config_ptr) ((hw_config_ptr)->hwmon.sys_voltage[0] != HWMON_SYS_VSENS_NULL_ID) 119 | #define platform_has_hwmon_fan_rpm(hw_config_ptr) ((hw_config_ptr)->hwmon.sys_fan_speed_rpm[0] != HWMON_SYS_FAN_NULL_ID) 120 | #define platform_has_hwmon_hdd_bpl(hw_config_ptr) ((hw_config_ptr)->hwmon.hdd_backplane[0] != HWMON_SYS_HDD_BP_NULL_ID) 121 | #define platform_has_hwmon_psu_status(hw_config_ptr) ((hw_config_ptr)->hwmon.psu_status[0] != HWMON_PSU_NULL_ID) 122 | #define platform_has_hwmon_current_sens(hw_config_ptr) ((hw_config_ptr)->hwmon.sys_current[0] != HWMON_SYS_CURR_NULL_ID) 123 | 124 | #endif //REDPILL_PLATFORM_TYPES_H 125 | -------------------------------------------------------------------------------- /config/platforms.h: -------------------------------------------------------------------------------- 1 | /* 2 | * DO NOT include this file anywhere besides runtime_config.c - its format is meant to be internal to the configuration 3 | * parsing. 4 | */ 5 | #ifndef REDPILLLKM_PLATFORMS_H 6 | #define REDPILLLKM_PLATFORMS_H 7 | 8 | #include "../shim/pci_shim.h" 9 | #include "platform_types.h" 10 | const struct hw_config supported_platforms[] = { 11 | { 12 | .name = "DS3615xs", 13 | .pci_stubs = { 14 | { .type = VPD_MARVELL_88SE9235, .bus = 0x07, .dev = 0x00, .fn = 0x00, .multifunction = false }, 15 | { .type = VPD_MARVELL_88SE9235, .bus = 0x08, .dev = 0x00, .fn = 0x00, .multifunction = false }, 16 | { .type = VPD_MARVELL_88SE9235, .bus = 0x09, .dev = 0x00, .fn = 0x00, .multifunction = false }, 17 | { .type = VPD_MARVELL_88SE9235, .bus = 0x0a, .dev = 0x00, .fn = 0x00, .multifunction = false }, 18 | { .type = __VPD_TERMINATOR__ } 19 | }, 20 | .emulate_rtc = false, 21 | .swap_serial = true, 22 | .reinit_ttyS0 = false, 23 | .fix_disk_led_ctrl = false, 24 | .has_cpu_temp = true, 25 | .hwmon = { 26 | .sys_thermal = { HWMON_SYS_TZONE_REMOTE1_ID, HWMON_SYS_TZONE_LOCAL_ID, HWMON_SYS_TZONE_REMOTE2_ID }, 27 | .sys_voltage = { HWMON_SYS_VSENS_VCC_ID, HWMON_SYS_VSENS_VPP_ID, HWMON_SYS_VSENS_V33_ID, 28 | HWMON_SYS_VSENS_V5_ID, HWMON_SYS_VSENS_V12_ID }, 29 | .sys_fan_speed_rpm = {HWMON_SYS_FAN1_ID, HWMON_SYS_FAN2_ID }, 30 | .hdd_backplane = { HWMON_SYS_HDD_BP_NULL_ID }, 31 | .psu_status = { HWMON_PSU_NULL_ID }, 32 | .sys_current = { HWMON_SYS_CURR_NULL_ID }, 33 | } 34 | }, 35 | { 36 | .name = "DS918+", 37 | .pci_stubs = { 38 | { .type = VPD_MARVELL_88SE9215, .bus = 0x01, .dev = 0x00, .fn = 0x00, .multifunction = false }, 39 | { .type = VPD_INTEL_I211, .bus = 0x02, .dev = 0x00, .fn = 0x00, .multifunction = false }, 40 | { .type = VPD_INTEL_I211, .bus = 0x03, .dev = 0x00, .fn = 0x00, .multifunction = false }, 41 | { .type = VPD_INTEL_CPU_AHCI_CTRL, .bus = 0x00, .dev = 0x12, .fn = 0x00, .multifunction = false }, 42 | { .type = VPD_INTEL_CPU_PCIE_PA, .bus = 0x00, .dev = 0x13, .fn = 0x00, .multifunction = false }, 43 | { .type = VPD_INTEL_CPU_PCIE_PB, .bus = 0x00, .dev = 0x14, .fn = 0x00, .multifunction = false }, 44 | { .type = VPD_INTEL_CPU_USB_XHCI, .bus = 0x00, .dev = 0x15, .fn = 0x00, .multifunction = false }, 45 | { .type = VPD_INTEL_CPU_I2C, .bus = 0x00, .dev = 0x16, .fn = 0x00, .multifunction = false }, 46 | { .type = VPD_INTEL_CPU_HSUART, .bus = 0x00, .dev = 0x18, .fn = 0x00, .multifunction = false }, 47 | { .type = VPD_INTEL_CPU_SPI, .bus = 0x00, .dev = 0x19, .fn = 0x02, .multifunction = true }, 48 | { .type = VPD_INTEL_CPU_SPI, .bus = 0x00, .dev = 0x19, .fn = 0x00, .multifunction = true }, 49 | { .type = VPD_INTEL_CPU_SMBUS, .bus = 0x00, .dev = 0x1f, .fn = 0x01, .multifunction = true }, 50 | { .type = VPD_INTEL_CPU_SMBUS, .bus = 0x00, .dev = 0x1f, .fn = 0x00, .multifunction = true }, 51 | 52 | { .type = __VPD_TERMINATOR__ } 53 | }, 54 | .emulate_rtc = true, 55 | .swap_serial = false, 56 | .reinit_ttyS0 = true, 57 | .fix_disk_led_ctrl = true, 58 | .has_cpu_temp = true, 59 | .hwmon = { 60 | .sys_thermal = { HWMON_SYS_TZONE_NULL_ID }, 61 | .sys_voltage = { HWMON_SYS_VSENS_NULL_ID }, 62 | .sys_fan_speed_rpm = { HWMON_SYS_FAN_NULL_ID }, 63 | .hdd_backplane = { HWMON_SYS_HDD_BP_DETECT_ID, HWMON_SYS_HDD_BP_ENABLE_ID }, 64 | .psu_status = { HWMON_PSU_NULL_ID }, 65 | .sys_current = { HWMON_SYS_CURR_NULL_ID }, 66 | } 67 | }, 68 | }; 69 | 70 | #endif //REDPILLLKM_PLATFORMS_H -------------------------------------------------------------------------------- /config/runtime_config.c: -------------------------------------------------------------------------------- 1 | #include "runtime_config.h" 2 | #include "platforms.h" 3 | #include "../common.h" 4 | #include "cmdline_delegate.h" 5 | #include "uart_defs.h" 6 | 7 | struct runtime_config current_config = { 8 | .hw = { '\0' }, 9 | .sn = { '\0' }, 10 | .boot_media = { 11 | .type = BOOT_MEDIA_USB, 12 | .mfg_mode = false, 13 | .vid = VID_PID_EMPTY, 14 | .pid = VID_PID_EMPTY, 15 | .dom_size_mib = 1024, //usually the image will be used with ESXi and thus it will be ~100MB anyway 16 | }, 17 | .port_thaw = true, 18 | .netif_num = 0, 19 | .macs = { '\0' }, 20 | .cmdline_blacklist = { '\0' }, 21 | .hw_config = NULL, 22 | }; 23 | 24 | static inline bool validate_sn(const serial_no *sn) { 25 | if (*sn[0] == '\0') { 26 | pr_loc_err("Serial number is empty"); 27 | return false; 28 | } 29 | 30 | //TODO: add more validation here, probably w/model? 31 | 32 | return true; 33 | } 34 | 35 | static __always_inline bool validate_boot_dev_usb(const struct boot_media *boot) 36 | { 37 | if (boot->vid == VID_PID_EMPTY && boot->pid == VID_PID_EMPTY) { 38 | pr_loc_wrn("Empty/no \"%s\" and \"%s\" specified - first USB storage device will be used", CMDLINE_CT_VID, 39 | CMDLINE_CT_PID); 40 | return true; //this isn't necessarily an error (e.g. running under a VM with only a single USB port) 41 | } 42 | 43 | if (boot->vid == VID_PID_EMPTY) { //PID=0 is valid, but the VID is not 44 | pr_loc_err("Empty/no \"%s\" specified", CMDLINE_CT_VID); 45 | return false; 46 | } 47 | 48 | pr_loc_dbg("Configured boot device type to USB"); 49 | return true; 50 | //not checking for >VID_PID_MAX as vid type is already ushort 51 | } 52 | 53 | static __always_inline bool validate_boot_dev_sata_dom(const struct boot_media *boot) 54 | { 55 | #ifndef NATIVE_SATA_DOM_SUPPORTED 56 | pr_loc_err("Kernel you are running a kernel was built without SATA DoM support, you cannot use %s%c. " 57 | "You can try booting with %s%c to enable experimental fake-SATA DoM.", 58 | CMDLINE_KT_SATADOM, CMDLINE_KT_SATADOM_NATIVE, 59 | CMDLINE_KT_SATADOM, CMDLINE_KT_SATADOM_FAKE); 60 | return false; 61 | #endif 62 | 63 | if (boot->vid != VID_PID_EMPTY || boot->pid != VID_PID_EMPTY) 64 | pr_loc_wrn("Using native SATA-DoM boot - %s and %s parameter values will be ignored", 65 | CMDLINE_CT_VID, CMDLINE_CT_PID); 66 | 67 | //this config is impossible as there's no equivalent for force-reinstall boot on SATA, so it's better to detect 68 | //that rather than causing WTFs for someone who falsely assuming that it's possible 69 | //However, it does work with fake-SATA boot (as it emulates USB disk anyway) 70 | if (boot->mfg_mode) { 71 | pr_loc_err("You cannot combine %s%c with %s - the OS supports force-reinstall on USB and fake SATA disk only", 72 | CMDLINE_KT_SATADOM, CMDLINE_KT_SATADOM_NATIVE, CMDLINE_CT_MFG); 73 | return false; 74 | } 75 | 76 | pr_loc_dbg("Configured boot device type to fake-SATA DOM"); 77 | return true; 78 | } 79 | 80 | static __always_inline bool validate_boot_dev_sata_disk(const struct boot_media *boot) 81 | { 82 | #ifdef NATIVE_SATA_DOM_SUPPORTED 83 | pr_loc_wrn("The kernel you are running supports native SATA DoM (%s%c). You're currently using an experimental " 84 | "fake-SATA DoM (%s%c) - consider switching to native SATA DoM (%s%c) for more stable operation.", 85 | CMDLINE_KT_SATADOM, CMDLINE_KT_SATADOM_NATIVE, 86 | CMDLINE_KT_SATADOM, CMDLINE_KT_SATADOM_FAKE, 87 | CMDLINE_KT_SATADOM, CMDLINE_KT_SATADOM_NATIVE); 88 | #endif 89 | 90 | if (boot->vid != VID_PID_EMPTY || boot->pid != VID_PID_EMPTY) 91 | pr_loc_wrn("Using fake SATA disk boot - %s and %s parameter values will be ignored", 92 | CMDLINE_CT_VID, CMDLINE_CT_PID); 93 | 94 | pr_loc_dbg("Configured boot device type to fake-SATA DOM"); 95 | return true; 96 | } 97 | 98 | static inline bool validate_boot_dev(const struct boot_media *boot) 99 | { 100 | switch (boot->type) { 101 | case BOOT_MEDIA_USB: 102 | return validate_boot_dev_usb(boot); 103 | case BOOT_MEDIA_SATA_DOM: 104 | return validate_boot_dev_sata_dom(boot); 105 | case BOOT_MEDIA_SATA_DISK: 106 | return validate_boot_dev_sata_disk(boot); 107 | default: 108 | pr_loc_bug("Got unknown boot type - did you forget to update %s after changing cmdline parsing?", 109 | __FUNCTION__); 110 | return false; 111 | 112 | } 113 | } 114 | 115 | static inline bool validate_nets(const unsigned short if_num, mac_address * const macs[MAX_NET_IFACES]) 116 | { 117 | size_t mac_len; 118 | unsigned short macs_num = 0; 119 | for (; macs_num < MAX_NET_IFACES; macs_num++) { 120 | if (!macs[macs_num]) 121 | break; //You cannot have gaps in macs array 122 | 123 | mac_len = strlen(*macs[macs_num]); 124 | if (mac_len != MAC_ADDR_LEN) { 125 | pr_loc_err("MAC address \"%s\" is invalid (expected %d characters, found %zu)", *macs[macs_num], MAC_ADDR_LEN, 126 | mac_len); 127 | } //else if validate if the MAC is actually semi-valid 128 | } 129 | 130 | bool valid = true; 131 | if (if_num == 0) { 132 | pr_loc_err("Number of defined interfaces (\"%s\") is not specified or empty", CMDLINE_KT_NETIF_NUM); 133 | valid = false; 134 | } 135 | 136 | if (macs_num == 0) { 137 | pr_loc_err("No MAC addressed are specified - use \"%s\" or \"%s\"...\"%s\" to set them", CMDLINE_KT_MACS, 138 | CMDLINE_KT_MAC1, CMDLINE_KT_MAC4); 139 | valid = false; 140 | } 141 | 142 | if (if_num != macs_num) { 143 | pr_loc_err("Number of defined interfaces (\"%s%d\") is not equal to the number of MAC addresses found (%d)", 144 | CMDLINE_KT_NETIF_NUM, if_num, macs_num); 145 | } 146 | 147 | return valid; 148 | } 149 | 150 | /** 151 | * This function validates consistency of the currently loaded platform config with the current environment 152 | * 153 | * Some options don't make sense unless the kernel was built with some specific configuration. This function aims to 154 | * detect common pitfalls in platforms configuration. This doesn't so much validate the platform definition per se 155 | * (but partially too) but the match between platform config chosen vs. kernel currently attempting to run that 156 | * platform. 157 | */ 158 | static inline bool validate_platform_config(const struct hw_config *hw) 159 | { 160 | #ifdef UART_BUG_SWAPPED 161 | const bool kernel_serial_swapped = true; 162 | #else 163 | const bool kernel_serial_swapped = false; 164 | #endif 165 | 166 | //This will not prevent the code from working, so it's not an error state by itself 167 | if (unlikely(hw->swap_serial && !kernel_serial_swapped)) 168 | pr_loc_bug("Your kernel indicates COM1 & COM2 ARE NOT swapped but your platform specifies swapping"); 169 | else if(unlikely(!hw->swap_serial && kernel_serial_swapped)) 170 | pr_loc_bug("Your kernel indicates COM1 & COM2 ARE swapped but your platform specifies NO swapping"); 171 | 172 | return true; 173 | } 174 | 175 | static int populate_hw_config(struct runtime_config *config) 176 | { 177 | //We cannot run with empty model or model which didn't match 178 | if (config->hw[0] == '\0') { 179 | pr_loc_crt("Empty model, please set \"%s\" parameter", CMDLINE_KT_HW); 180 | return -ENOENT; 181 | } 182 | 183 | for (int i = 0; i < ARRAY_SIZE(supported_platforms); i++) { 184 | if (strcmp(supported_platforms[i].name, (char *)config->hw) != 0) 185 | continue; 186 | 187 | pr_loc_dbg("Found platform definition for \"%s\"", config->hw); 188 | config->hw_config = &supported_platforms[i]; 189 | return 0; 190 | } 191 | 192 | pr_loc_crt("The model set using \"%s%s\" is not valid", CMDLINE_KT_HW, config->hw); 193 | return -EINVAL; 194 | } 195 | 196 | static bool validate_runtime_config(const struct runtime_config *config) 197 | { 198 | pr_loc_dbg("Validating runtime config..."); 199 | bool valid = true; 200 | 201 | valid &= validate_sn(&config->sn); 202 | valid &= validate_boot_dev(&config->boot_media); 203 | valid &= validate_nets(config->netif_num, config->macs); 204 | valid &= validate_platform_config(config->hw_config); 205 | 206 | if (valid) { 207 | pr_loc_dbg("Config validation resulted in %s", valid ? "OK" : "ERR"); 208 | return 0; 209 | } else { 210 | pr_loc_err("Config validation FAILED"); 211 | return -EINVAL; 212 | } 213 | } 214 | 215 | int populate_runtime_config(struct runtime_config *config) 216 | { 217 | int out = 0; 218 | 219 | if ((out = populate_hw_config(config)) != 0 || (out = validate_runtime_config(config)) != 0) { 220 | pr_loc_err("Failed to populate runtime config!"); 221 | return out; 222 | } 223 | 224 | pr_loc_inf("Runtime config populated"); 225 | 226 | return out; 227 | } 228 | 229 | void free_runtime_config(struct runtime_config *config) 230 | { 231 | for (int i = 0; i < MAX_NET_IFACES; i++) { 232 | if (config->macs[i]) { 233 | pr_loc_dbg("Free MAC%d @ %p", i, config->macs[i]); 234 | kfree(config->macs[i]); 235 | } 236 | } 237 | 238 | for (int i = 0; i < MAX_BLACKLISTED_CMDLINE_TOKENS; i++) { 239 | if (config->cmdline_blacklist[i]) { 240 | pr_loc_dbg("Free cmdline blacklist entry %d @ %p", i, config->cmdline_blacklist[i]); 241 | kfree(config->cmdline_blacklist[i]); 242 | } 243 | } 244 | 245 | pr_loc_inf("Runtime config freed"); 246 | } 247 | -------------------------------------------------------------------------------- /config/runtime_config.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILLLKM_RUNTIME_CONFIG_H 2 | #define REDPILLLKM_RUNTIME_CONFIG_H 3 | 4 | #include "uart_defs.h" //UART config values 5 | #include //bool 6 | 7 | //These below are currently known runtime limitations 8 | #define MAX_NET_IFACES 8 9 | #define MAC_ADDR_LEN 12 10 | #define MAX_BLACKLISTED_CMDLINE_TOKENS 10 11 | 12 | #ifdef CONFIG_SYNO_BOOT_SATA_DOM 13 | #define NATIVE_SATA_DOM_SUPPORTED //whether SCSI sd.c driver supports native SATA DOM 14 | #endif 15 | 16 | //UART-related constants were moved to uart_defs.h, to allow subcomponents to importa a smaller subset than this header 17 | #define MODEL_MAX_LENGTH 10 18 | #define SN_MAX_LENGTH 13 19 | 20 | #define VID_PID_EMPTY 0x0000 21 | #define VID_PID_MAX 0xFFFF 22 | 23 | typedef unsigned short device_id; 24 | typedef char syno_hw[MODEL_MAX_LENGTH + 1]; 25 | typedef char mac_address[MAC_ADDR_LEN + 1]; 26 | typedef char serial_no[SN_MAX_LENGTH + 1]; 27 | typedef char cmdline_token[]; 28 | 29 | enum boot_media_type { 30 | BOOT_MEDIA_USB, 31 | BOOT_MEDIA_SATA_DOM, 32 | BOOT_MEDIA_SATA_DISK, 33 | }; 34 | 35 | struct boot_media { 36 | enum boot_media_type type; // Default: BOOT_MEDIA_USB 37 | 38 | //USB only options 39 | bool mfg_mode; //emulate mfg mode (valid for USB boot only). Default: false 40 | device_id vid; //Vendor ID of device containing the loader. Default: empty 41 | device_id pid; //Product ID of device containing the loader. Default: empty 42 | 43 | //SATA only options 44 | unsigned long dom_size_mib; //Max size of SATA DOM Default: 1024 45 | }; 46 | 47 | struct hw_config; 48 | struct runtime_config { 49 | syno_hw hw; //used to determine quirks. Default: empty 50 | serial_no sn; //Used to validate it and warn the user. Default: empty 51 | struct boot_media boot_media; 52 | bool port_thaw; //Currently unknown. Default: true 53 | unsigned short netif_num; //Number of eth interfaces. Default: 0 54 | mac_address *macs[MAX_NET_IFACES]; //MAC addresses of eth interfaces. Default: [] 55 | cmdline_token *cmdline_blacklist[MAX_BLACKLISTED_CMDLINE_TOKENS];// Default: [] 56 | const struct hw_config *hw_config; 57 | }; 58 | extern struct runtime_config current_config; 59 | 60 | /** 61 | * Takes a raw extracted config and "shakes it a little bit" by validating things & constructing dependent structures 62 | * 63 | * Warning: if this function returns false YOU MUST NOT trust the config structure. Other code WILL break as it assumes 64 | * the config is valid (e.g. doesn't have null ptrs which this function generates). 65 | * Also, after you call this function you should call free_runtime_config() to clear up memory reservations. 66 | */ 67 | int populate_runtime_config(struct runtime_config *config); 68 | 69 | void free_runtime_config(struct runtime_config *config); 70 | 71 | #endif //REDPILLLKM_RUNTIME_CONFIG_H 72 | -------------------------------------------------------------------------------- /config/uart_defs.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is meant to be small and portable. It can be included by other parts of the module wishing to get some info 3 | * about UARTs. It should not contain any extensive definitions or static structures reservation. It is mostly 4 | * extracting information buried in the Linux serial subsystem into usable constants. 5 | */ 6 | #ifndef REDPILL_UART_DEFS_H 7 | #define REDPILL_UART_DEFS_H 8 | 9 | #include //flags for pc_com* 10 | #include //struct uart_port 11 | #include //KERNEL_VERSION() 12 | 13 | //These definitions are taken from asm/serial.h for a normal (i.e. non-swapped) UART1/COM1 port on an x86 PC 14 | #define STD_COM1_IOBASE 0x3f8 15 | #define STD_COM1_IRQ 4 16 | #define STD_COM2_IOBASE 0x2f8 17 | #define STD_COM2_IRQ 3 18 | #define STD_COM3_IOBASE 0x3e8 19 | #define STD_COM3_IRQ 4 20 | #define STD_COM4_IOBASE 0x2e8 21 | #define STD_COM4_IRQ 3 22 | 23 | //They changed name of flags const: https://github.com/torvalds/linux/commit/196cf358422517b3ff3779c46a1f3e26fb084172 24 | #if LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0) 25 | #define STD_COMX_FLAGS STD_COM_FLAGS 26 | #endif 27 | 28 | #define STD_COMX_BAUD BASE_BAUD 29 | 30 | #define STD_COMX_DEV_NAME "ttyS" 31 | #define SRD_COMX_BAUD_OPTS "115200n8" 32 | 33 | #define UART_NR CONFIG_SERIAL_8250_NR_UARTS 34 | #define SERIAL8250_LAST_ISA_LINE (UART_NR-1) //max valid index of ttyS 35 | #define SERIAL8250_SOFT_IRQ 0 //a special IRQ value which, if set on a port, will force 8250 driver to use timers 36 | 37 | 38 | #ifdef CONFIG_SYNO_X86_SERIAL_PORT_SWAP 39 | #define UART_BUG_SWAPPED //indicates that first two UARTs are swapped (sic!). Yes, we do consider it a fucking bug. 40 | #endif 41 | 42 | #endif //REDPILL_UART_DEFS_H 43 | -------------------------------------------------------------------------------- /config/vpci_types.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_VPCI_LIMITS_H 2 | #define REDPILL_VPCI_LIMITS_H 3 | 4 | #include "../shim/pci_shim.h" //pci_shim_device_type 5 | 6 | //Defines below are experimentally determined to be sufficient but can often be changed 7 | #define MAX_VPCI_BUSES 8 //adjust if needed, max 256 8 | #define MAX_VPCI_DEVS 16 //adjust if needed, max 256*32=8192 9 | 10 | struct vpci_device_stub { 11 | enum pci_shim_device_type type; 12 | u8 bus; 13 | u8 dev; 14 | u8 fn; 15 | bool multifunction:1; 16 | }; 17 | 18 | #endif //REDPILL_VPCI_LIMITS_H 19 | -------------------------------------------------------------------------------- /debug/debug_execve.c: -------------------------------------------------------------------------------- 1 | #include "debug_execve.h" 2 | #include "../common.h" 3 | #include //task_struct 4 | #include //get_user 5 | #include //compat_uptr_t 6 | #include //MAX_ARG_STRINGS 7 | 8 | /* 9 | * Struct copied 1:1 from: 10 | * 11 | * linux/fs/exec.c 12 | * 13 | * Copyright (C) 1991, 1992 Linus Torvalds 14 | */ 15 | struct user_arg_ptr { 16 | #ifdef CONFIG_COMPAT 17 | bool is_compat; 18 | #endif 19 | union { 20 | const char __user *const __user *native; 21 | #ifdef CONFIG_COMPAT 22 | const compat_uptr_t __user *compat; 23 | #endif 24 | } ptr; 25 | }; 26 | 27 | /* 28 | * Function copied 1:1 from: 29 | * 30 | * linux/fs/exec.c 31 | * 32 | * Copyright (C) 1991, 1992 Linus Torvalds 33 | */ 34 | static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr) 35 | { 36 | const char __user *native; 37 | 38 | #ifdef CONFIG_COMPAT 39 | if (unlikely(argv.is_compat)) { 40 | compat_uptr_t compat; 41 | 42 | if (get_user(compat, argv.ptr.compat + nr)) 43 | return ERR_PTR(-EFAULT); 44 | 45 | return compat_ptr(compat); 46 | } 47 | #endif 48 | 49 | if (get_user(native, argv.ptr.native + nr)) 50 | return ERR_PTR(-EFAULT); 51 | 52 | return native; 53 | } 54 | 55 | /* 56 | * Modified for simplicity from count() in: 57 | * 58 | * linux/fs/exec.c 59 | * 60 | * Copyright (C) 1991, 1992 Linus Torvalds 61 | */ 62 | static int count_args(struct user_arg_ptr argv) 63 | { 64 | if (argv.ptr.native == NULL) 65 | return 0; 66 | 67 | int i = 0; 68 | 69 | for (;;) { 70 | const char __user *p = get_user_arg_ptr(argv, i); 71 | 72 | if (!p) 73 | break; 74 | 75 | if (IS_ERR(p) || i >= MAX_ARG_STRINGS) 76 | return -EFAULT; 77 | 78 | ++i; 79 | 80 | if (fatal_signal_pending(current)) 81 | return -ERESTARTNOHAND; 82 | cond_resched(); 83 | } 84 | 85 | return i; 86 | } 87 | 88 | static void inline fixup_arg_str(char *arg_ptr, int cur_argc, const char *what) 89 | { 90 | pr_loc_wrn("Failed to copy %d arg - %s failed", cur_argc, what); 91 | memcpy(arg_ptr, "..?\0", 4); 92 | } 93 | 94 | void RPDBG_print_execve_call(const char *filename, const char __user *const __user *argv) 95 | { 96 | struct task_struct *caller = get_cpu_var(current_task); 97 | 98 | struct user_arg_ptr argv_up = { .ptr.native = argv }; 99 | int argc = count_args(argv_up); 100 | 101 | char *arg_str = kzalloc(MAX_ARG_STRLEN, GFP_KERNEL); 102 | if (unlikely(!arg_str)) { 103 | pr_loc_crt("kzalloc failed"); 104 | return; 105 | } 106 | 107 | char *arg_ptr = &arg_str[0]; 108 | for (int i = 0; i < argc; i++) { 109 | const char __user *p = get_user_arg_ptr(argv_up, i); 110 | if (IS_ERR(p)) { 111 | fixup_arg_str(arg_ptr, i, "get_user_arg_ptr"); 112 | goto out_free; 113 | } 114 | 115 | int len = strnlen_user(p, MAX_ARG_STRLEN); //includes nullbyte 116 | if (!len) { 117 | fixup_arg_str(arg_ptr, i, "strnlen_user"); 118 | goto out_free; 119 | } 120 | --len; //we want to copy without nullbyte as we handle it ourselves while attaching to arg_ptr 121 | 122 | if (copy_from_user(arg_ptr, p, len)) { 123 | fixup_arg_str(arg_ptr, i, "copy_from_user"); 124 | goto out_free; 125 | } 126 | 127 | arg_ptr += len; 128 | *arg_ptr = (i + 1 == argc) ? '\0' : ' '; //separate by spaces UNLESS it's the last argument 129 | ++arg_ptr; 130 | } 131 | 132 | out_free: 133 | pr_loc_dbg("execve@cpu%d: %s[%d]=>%s[%d] {%s}", caller->on_cpu, caller->comm, caller->pid, filename, argc, arg_str); 134 | kfree(arg_str); 135 | } -------------------------------------------------------------------------------- /debug/debug_execve.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_DEBUG_EXECVE_H 2 | #define REDPILL_DEBUG_EXECVE_H 3 | 4 | void RPDBG_print_execve_call(const char *filename, const char *const *argv); 5 | 6 | #endif //REDPILL_DEBUG_EXECVE_H 7 | -------------------------------------------------------------------------------- /debug/debug_vuart.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_DEBUG_VUART_H 2 | #define REDPILL_DEBUG_VUART_H 3 | 4 | //Whether the code will print all internal state changes 5 | #ifdef VUART_DEBUG_LOG 6 | //Main print macro used everywhere below 7 | #define uart_prdbg(f, ...) pr_loc_dbg(f, ##__VA_ARGS__) 8 | 9 | #define reg_read(rN) uart_prdbg("Reading " rN " registry"); 10 | #define reg_write(rN) uart_prdbg("Writing " rN " registry"); 11 | #define reg_read_dump(d, rF, rN) reg_read(rN); dump_##rF(d); 12 | #define reg_write_dump(d, rF, rN) reg_write(rN); dump_##rF(d); 13 | #define dri(vdev, reg, flag) ((vdev)->reg&(flag)) ? 1:0 //Dump Register as 1-0 Integer 14 | #define diiri(vdev, flag) (((vdev)->iir&UART_IIR_ID) == (flag)) ? 1:0 //Dump IIR Interrupt type as 1-0 integer 15 | #define dump_ier(d) \ 16 | uart_prdbg("IER[0x%02x]: DR_int=%d | THRe_int=%d | RLS_int=%d | " \ 17 | "MS_int=%d", \ 18 | (d)->ier, dri(d,ier,UART_IER_RDI), dri(d,ier,UART_IER_THRI), dri(d,ier,UART_IER_RLSI), \ 19 | dri(d,ier,UART_IER_MSI)); 20 | //Be careful interpreting the result of this macro - no_int_pend means "no interrupts pending" (so 0 if there are 21 | // pending interrupts and 1 if there are no interrupts pending); see Table 3-5 in TI doc 22 | //Also FIFO flags are slightly weird (it's 2 bit, see IIR table in https://en.wikibooks.org/wiki/Serial_Programming/8250_UART_Programming) 23 | // so fifoen=0_0 means "FIFO disabled", fifoen=1_1 means "FIFO enabled", and fifoen=0_1 means "FIFO enabled & broken" 24 | //Also, since MSI is 0-0-0 it's a special-ish case: it's only considered enabled when int is pending and all bits are 0 25 | #define dump_iir(d) \ 26 | uart_prdbg("IIR/ISR[0x%02x]: no_int_pend=%d | int_MS=%d | " \ 27 | "int_THRe=%d | int_DR=%d | int_RLS=%d | " \ 28 | "fifoen=%d_%d", \ 29 | (d)->iir, dri(d,iir,UART_IIR_NO_INT), (!((d)->iir&UART_IIR_NO_INT)&&((d)->iir&UART_IIR_ID)==UART_IIR_MSI)?1:0, \ 30 | diiri(d,UART_IIR_THRI), diiri(d,UART_IIR_RDI), diiri(d,UART_IIR_RLSI), \ 31 | (((d)->iir & UART_IIR_FIFEN_B6)?1:0), (((d)->iir & UART_IIR_FIFEN_B7)?1:0)); 32 | #define dump_fcr(d) \ 33 | uart_prdbg("FCR[0x%02x]: FIFOon=%d | RxFIFOrst=%d | " \ 34 | "TxFIFOrst=%d | EnDMAend=%d", \ 35 | (d)->fcr, dri(d,fcr,UART_FCR_ENABLE_FIFO), dri(d,fcr,UART_FCR_CLEAR_RCVR), \ 36 | dri(d,fcr,UART_FCR_CLEAR_XMIT), dri(d,fcr,UART_FCR_DMA_SELECT)); 37 | #define dump_lcr(d) \ 38 | uart_prdbg("LCR[0x%02x]: Stop=%d | PairEN=%d | EvenP=%d | " \ 39 | "ForcPair=%d | SetBrk=%d | DLAB=%d", \ 40 | (d)->lcr, dri(d,lcr,UART_LCR_STOP), dri(d,lcr,UART_LCR_PARITY), dri(d,lcr,UART_LCR_EPAR), \ 41 | dri(d,lcr,UART_LCR_SPAR), dri(d,lcr,UART_LCR_SBC), dri(d,lcr,UART_LCR_DLAB)); 42 | #define dump_mcr(d) \ 43 | uart_prdbg("MCR[0x%02x]: DTR=%d | RTS=%d | Out1=%d | " \ 44 | "Out2/IntE=%d | Loop=%d", \ 45 | (d)->mcr, dri(d,mcr,UART_MCR_DTR), dri(d,mcr,UART_MCR_RTS), dri(d,mcr,UART_MCR_OUT1), \ 46 | dri(d,mcr,UART_MCR_OUT2), dri(d,mcr,UART_MCR_LOOP)); 47 | #define dump_lsr(d) \ 48 | uart_prdbg("LSR[0x%02x]: data_ready=%d | ovrunE=%d | pairE=%d | " \ 49 | "frE=%d | break_req=%d | THRemp=%d | TransEMP=%d | " \ 50 | "FIFOdE=%d", \ 51 | (d)->lsr, dri(d,lsr,UART_LSR_DR), dri(d,lsr,UART_LSR_OE), dri(d,lsr,UART_LSR_PE), \ 52 | dri(d,lsr,UART_LSR_FE), dri(d,lsr,UART_LSR_BI), dri(d,lsr,UART_LSR_THRE), dri(d,lsr,UART_LSR_TEMT), \ 53 | dri(d,lsr,UART_LSR_FIFOE)); 54 | #define dump_msr(d) \ 55 | uart_prdbg("MSR[0x%02x]: delCTS=%d | delDSR=%d | trEdgRI=%d | " \ 56 | "delCD=%d | CTS=%d | DSR=%d | RI=%d | " \ 57 | "DCD=%d", \ 58 | (d)->msr, dri(d,msr,UART_MSR_DCTS), dri(d,msr,UART_MSR_DDSR), dri(d,msr,UART_MSR_TERI), \ 59 | dri(d,msr,UART_MSR_DDCD), dri(d,msr,UART_MSR_CTS), dri(d,msr,UART_MSR_DSR), dri(d,msr,UART_MSR_RI), \ 60 | dri(d,msr,UART_MSR_DCD)); 61 | 62 | #else //VUART_DEBUG_LOG disabled \/ 63 | #define uart_prdbg(f, ...) { /* noop */ } 64 | #define reg_read(rN) { /* noop */ } 65 | #define reg_write(rN) { /* noop */ } 66 | #define reg_read_dump(d, rF, rN) { /* noop */ } 67 | #define reg_write_dump(d, rF, rN) { /* noop */ } 68 | #define dump_ier(d) { /* noop */ } 69 | #define dump_iir(d) { /* noop */ } 70 | #define dump_fcr(d) { /* noop */ } 71 | #define dump_lcr(d) { /* noop */ } 72 | #define dump_mcr(d) { /* noop */ } 73 | #define dump_lsr(d) { /* noop */ } 74 | #define dump_msr(d) { /* noop */ } 75 | #endif //VUART_DEBUG_LOG 76 | 77 | #endif //REDPILL_DEBUG_VUART_H 78 | -------------------------------------------------------------------------------- /internal/call_protected.c: -------------------------------------------------------------------------------- 1 | #include "call_protected.h" 2 | #include "../common.h" 3 | #include //common exit codes 4 | #include //kallsyms_lookup_name() 5 | #include //symbol_get()/put 6 | 7 | //This will eventually stop working (since Linux >=5.7.0 has the kallsyms_lookup_name() removed) 8 | //Workaround will be needed: https://github.com/xcellerator/linux_kernel_hacking/issues/3 9 | 10 | #define __VOID_RETURN__ 11 | //This macro should be used to export symbols which aren't normally EXPORT_SYMBOL/EXPORT_SYMBOL_GPL in the kernel but 12 | // they exist within the kernel (and not a loadable module!). Keep in mind that most of the time "static" cannot be 13 | // reexported using this trick. 14 | //All re-exported function will have _ prefix (e.g. foo() becomes _foo()) 15 | #define DEFINE_UNEXPORTED_SHIM(return_type, org_function_name, call_args, call_vars, fail_return) \ 16 | extern asmlinkage return_type org_function_name(call_args); \ 17 | typedef typeof(org_function_name) *org_function_name##__ret; \ 18 | static unsigned long org_function_name##__addr = 0; \ 19 | return_type _##org_function_name(call_args) \ 20 | { \ 21 | if (unlikely(org_function_name##__addr == 0)) { \ 22 | org_function_name##__addr = kallsyms_lookup_name(#org_function_name); \ 23 | if (org_function_name##__addr == 0) { \ 24 | pr_loc_bug("Failed to fetch %s() syscall address", #org_function_name); \ 25 | return fail_return; \ 26 | } \ 27 | pr_loc_dbg("Got addr %lx for %s", org_function_name##__addr, #org_function_name); \ 28 | } \ 29 | \ 30 | return ((org_function_name##__ret)org_function_name##__addr)(call_vars); \ 31 | } 32 | 33 | //This macro should be used to export symbols which aren't normally EXPORT_SYMBOL/EXPORT_SYMBOL_GPL in the kernel but 34 | // they exist within the kernel and are defined as __init. These symbol can only be called when the system is still 35 | // booting (i.e. before init user-space binary was called). After that calling such functions is a lottery - the memory 36 | // of them is freed by free_initmem() [called in main.c:kernel_init()]. That's why we skip any caching kere as these are 37 | // called mostly as a one-off during boot process when this module was loaded as a I/O scheduler. 38 | //All re-exported function will have _ prefix (e.g. foo() becomes _foo()) 39 | #define DEFINE_UNEXPORTED_INIT_SHIM(return_type, org_function_name, call_args, call_vars, fail_return) \ 40 | extern asmlinkage return_type org_function_name(call_args); \ 41 | typedef typeof(org_function_name) *org_function_name##__ret; \ 42 | return_type _##org_function_name(call_args) \ 43 | { \ 44 | unsigned long org_function_name##__addr = 0; \ 45 | if (unlikely(!is_system_booting())) { \ 46 | pr_loc_bug("Attempted to call %s() when the system is already booted (state=%d)", \ 47 | #org_function_name, system_state); \ 48 | return fail_return; \ 49 | } \ 50 | \ 51 | org_function_name##__addr = kallsyms_lookup_name(#org_function_name); \ 52 | if (org_function_name##__addr == 0) { \ 53 | pr_loc_bug("Failed to fetch %s() syscall address", #org_function_name); \ 54 | return fail_return; \ 55 | } \ 56 | pr_loc_dbg("Got addr %lx for %s", org_function_name##__addr, #org_function_name); \ 57 | \ 58 | return ((org_function_name##__ret)org_function_name##__addr)(call_vars); \ 59 | } 60 | 61 | //This macro should be used to export symbols which are normally exported by modules in situations where this module 62 | // must be loaded before such module exporting the symbol. 63 | //Normally if symbol for module "X" is used in "Y" the kernel will complain that "X" muse be loaded before "Y". 64 | //All re-exported function will have _ prefix (e.g. foo() becomes _foo()) 65 | #define DEFINE_DYNAMIC_SHIM(return_type, org_function_name, call_args, call_vars, fail_return) \ 66 | extern asmlinkage return_type org_function_name(call_args); \ 67 | typedef typeof(org_function_name) *org_function_name##__ret; \ 68 | return_type _##org_function_name(call_args) \ 69 | { \ 70 | org_function_name##__ret org_function_name##__ptr = (org_function_name##__ret)__symbol_get(#org_function_name); \ 71 | if (!org_function_name##__ptr) { \ 72 | pr_loc_bug("Failed to fetch %s() symbol (is that module loaded?)", #org_function_name); \ 73 | return fail_return; \ 74 | } \ 75 | pr_loc_dbg("Got ptr %p for %s", org_function_name##__ptr, #org_function_name); \ 76 | /*Doing this BEFORE the call makes a TINY window where the symbol can "escape" but it's protects from deadlock*/\ 77 | __symbol_put(#org_function_name); \ 78 | \ 79 | return ((org_function_name##__ret)org_function_name##__ptr)(call_vars); \ 80 | } 81 | //********************************************************************************************************************// 82 | 83 | DEFINE_UNEXPORTED_SHIM(int, cmdline_proc_show, CP_LIST(struct seq_file *m, void *v), CP_LIST(m, v), -EFAULT); 84 | DEFINE_UNEXPORTED_SHIM(void, flush_tlb_all, CP_LIST(void), CP_LIST(), __VOID_RETURN__); 85 | 86 | //See header file for detailed explanation what's going on here as it's more complex than a single commit 87 | #if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0) 88 | DEFINE_UNEXPORTED_SHIM(int, do_execve, CP_LIST(const char *filename, 89 | const char __user *const __user *__argv, 90 | const char __user *const __user *__envp), CP_LIST(filename, __argv, __envp), -EINTR); 91 | 92 | #ifndef CONFIG_AUDITSYSCALL 93 | DEFINE_UNEXPORTED_SHIM(void, final_putname, CP_LIST(struct filename *name), CP_LIST(name), __VOID_RETURN__); 94 | #else 95 | DEFINE_UNEXPORTED_SHIM(void, putname, CP_LIST(struct filename *name), CP_LIST(name), __VOID_RETURN__); 96 | #endif 97 | #else 98 | DEFINE_UNEXPORTED_SHIM(int, do_execve, CP_LIST(struct filename *filename, 99 | const char __user *const __user *__argv, 100 | const char __user *const __user *__envp), CP_LIST(filename, __argv, __envp), -EINTR); 101 | DEFINE_UNEXPORTED_SHIM(struct filename *, getname, CP_LIST(const char __user *name), CP_LIST(name), ERR_PTR(-EFAULT)); 102 | #endif 103 | 104 | DEFINE_UNEXPORTED_SHIM(int, scsi_scan_host_selected, CP_LIST(struct Scsi_Host *shost, unsigned int channel, unsigned int id, u64 lun, int rescan), CP_LIST(shost, channel, id, lun, rescan), -EIO); 105 | DEFINE_UNEXPORTED_SHIM(int, ida_pre_get, CP_LIST(struct ida *ida, gfp_t gfp_mask), CP_LIST(ida, gfp_mask), -EINVAL); 106 | 107 | DEFINE_UNEXPORTED_SHIM(int, early_serial_setup, CP_LIST(struct uart_port *port), port, -EIO); 108 | DEFINE_UNEXPORTED_SHIM(int, serial8250_find_port, CP_LIST(struct uart_port *p), CP_LIST(p), -EIO); 109 | 110 | DEFINE_UNEXPORTED_INIT_SHIM(int, elevator_setup, CP_LIST(char *str), CP_LIST(str), -EINVAL); 111 | 112 | DEFINE_DYNAMIC_SHIM(void, usb_register_notify, CP_LIST(struct notifier_block *nb), CP_LIST(nb), __VOID_RETURN__); 113 | DEFINE_DYNAMIC_SHIM(void, usb_unregister_notify, CP_LIST(struct notifier_block *nb), CP_LIST(nb), __VOID_RETURN__); 114 | -------------------------------------------------------------------------------- /internal/call_protected.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILLLKM_CALL_PROTECTED_H 2 | #define REDPILLLKM_CALL_PROTECTED_H 3 | 4 | #include //LINUX_VERSION_CODE, KERNEL_VERSION 5 | #include //bool 6 | #include //system_states & system_state 7 | 8 | // *************************************** Useful macros *************************************** // 9 | //Check if the system is still in booting stage (useful when you want to call __init functions as they're deleted) 10 | #define is_system_booting() (system_state == SYSTEM_BOOTING) 11 | 12 | // ************************** Exports of normally protected functions ************************** // 13 | 14 | //A usual macros to make defining them easier & consistent with .c implementation 15 | #define CP_LIST(...) __VA_ARGS__ //used to pass a list of arguments as a single argument 16 | #define CP_DECLARE_SHIM(return_type, org_function_name, call_args) return_type _##org_function_name(call_args); 17 | 18 | struct seq_file; 19 | CP_DECLARE_SHIM(int, cmdline_proc_show, CP_LIST(struct seq_file *m, void *v)); //extracts kernel cmdline 20 | CP_DECLARE_SHIM(void, flush_tlb_all, CP_LIST(void)); //used to flush caches in memory.c operations 21 | 22 | /* Thanks Jeff... https://groups.google.com/g/kernel-meetup-bangalore/c/rvQccTl_3kc/m/BJCnnXGCAgAJ 23 | * In case the link disappears: Jeff Layton from RedHat decided to just nuke the getname() API after 7 years of it being 24 | * exposed in the kernel. So in practice we need to use kallsyms to get it on kernels >=3.14 (up to current 5.14) 25 | * See https://github.com/torvalds/linux/commit/9115eac2c788c17b57c9256cb322fa7371972ddf 26 | * Another unrelated change which happened in v3.14 was that when "struct filename*" is passed the callee is responsible 27 | * for freeing it (using putname()). However, in older versions we (the caller) needs to free it 28 | * See https://github.com/torvalds/linux/commit/c4ad8f98bef77c7356aa6a9ad9188a6acc6b849d 29 | * 30 | * This whole block deals with functions needed for execve() shimming 31 | */ 32 | struct filename; 33 | #if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0) 34 | CP_DECLARE_SHIM(int, do_execve, CP_LIST(const char *filename, 35 | const char __user *const __user *__argv, 36 | const char __user *const __user *__envp)); 37 | 38 | #include 39 | #define _getname(...) getname(__VA_ARGS__) 40 | 41 | //If syscall audit is disabled putname is an alias to final_putname(), see include/linux/fs.h; later on this was changed 42 | // but this branch needs to handle only <3.14 as we don't need (and shouldn't use!) putname() in >=3.14 43 | #ifndef CONFIG_AUDITSYSCALL //if CONFIG_AUDITSYSCALL disabled we unexport final_putname and add putname define line fs.h 44 | #define _putname(name) _final_putname(name) 45 | CP_DECLARE_SHIM(void, final_putname, CP_LIST(struct filename *name)); 46 | #else //if the CONFIG_AUDITSYSCALL is enabled we need to proxy to traced putname to make sure references are counted 47 | CP_DECLARE_SHIM(void, putname, CP_LIST(struct filename *name)); 48 | #endif 49 | #else 50 | CP_DECLARE_SHIM(int, do_execve, CP_LIST(struct filename *filename, 51 | const char __user *const __user *__argv, 52 | const char __user *const __user *__envp)); 53 | CP_DECLARE_SHIM(struct filename *, getname, CP_LIST(const char __user *name)); 54 | #endif 55 | 56 | //The following functions are used by vUART and uart_fixer 57 | typedef struct uart_port *uart_port_p; 58 | CP_DECLARE_SHIM(int, early_serial_setup, CP_LIST(struct uart_port *port)); 59 | CP_DECLARE_SHIM(int, serial8250_find_port, CP_LIST(struct uart_port *p)); 60 | 61 | //Exported so that we can forcefully rescan the SCSI host in scsi_toolbox. This operation is normally available in 62 | // userland when you're a root, but somehow they missed an export for kernel code (which according to kernel rules is a 63 | // bug, but probably nobody asked before) 64 | struct Scsi_Host; 65 | CP_DECLARE_SHIM(int, scsi_scan_host_selected, 66 | CP_LIST(struct Scsi_Host *shost, unsigned int channel, unsigned int id, u64 lun, int rescan)); 67 | 68 | struct ida; 69 | CP_DECLARE_SHIM(int, ida_pre_get, CP_LIST(struct ida *ida, gfp_t gfp_mask)); 70 | 71 | //Used for fixing I/O scheduler if module was loaded using elevator= and broke it 72 | CP_DECLARE_SHIM(int, elevator_setup, CP_LIST(char *str)); 73 | 74 | struct notifier_block; 75 | CP_DECLARE_SHIM(void, usb_register_notify, CP_LIST(struct notifier_block *nb)); 76 | CP_DECLARE_SHIM(void, usb_unregister_notify, CP_LIST(struct notifier_block *nb)); 77 | #endif //REDPILLLKM_CALL_PROTECTED_H 78 | -------------------------------------------------------------------------------- /internal/helper/math_helper.c: -------------------------------------------------------------------------------- 1 | #include "math_helper.h" 2 | #include //prandom_u32() 3 | 4 | int prandom_int_range_stable(int *cur_val, int dev, int min, int max) 5 | { 6 | if (likely(*cur_val != 0)) { 7 | int new_min = (*cur_val) - dev; 8 | int new_max = (*cur_val) + dev; 9 | min = new_min < min ? min : new_min; 10 | max = new_max > max ? max : new_max; 11 | } 12 | 13 | *cur_val = prandom_int_range(min, max); 14 | 15 | return *cur_val; 16 | } -------------------------------------------------------------------------------- /internal/helper/math_helper.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_MATH_HELPER_H 2 | #define REDPILL_MATH_HELPER_H 3 | 4 | /** 5 | * Generates pseudo-random integer in a range specified 6 | * 7 | * @param min Lower boundary integer 8 | * @param max Higher boundary integer 9 | * 10 | * @return pseudorandom integer up to 32 bits in length 11 | */ 12 | #define prandom_int_range(min, max) ({ \ 13 | int _rand = (prandom_u32() % ((max) + 1 - (min)) + (min)); \ 14 | _rand; \ 15 | }) 16 | 17 | /** 18 | * Generates temporally stable pseudo-random integer in a range specified 19 | * 20 | * @param cur_val Pointer to store/read current value; set its value to 0 initially to generate setpoint automatically 21 | * @param dev Max deviation from the current value 22 | * @param min Lower boundary integer 23 | * @param max Higher boundary integer 24 | * 25 | * @return pseudorandom integer up to 32 bits in length 26 | */ 27 | int prandom_int_range_stable(int *cur_val, int dev, int min, int max); 28 | 29 | #endif //REDPILL_MATH_HELPER_H 30 | -------------------------------------------------------------------------------- /internal/helper/memory_helper.c: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO: look into ovrride_symbol to check if there's any docs 3 | */ 4 | #include "memory_helper.h" 5 | #include "../../common.h" 6 | #include "../call_protected.h" //_flush_tlb_all() 7 | #include //PAGE_ALIGN 8 | #include //PAGE_SIZE 9 | #include //_PAGE_RW 10 | 11 | #define PAGE_ALIGN_BOTTOM(addr) (PAGE_ALIGN(addr) - PAGE_SIZE) //aligns the memory address to bottom of the page boundary 12 | #define NUM_PAGES_BETWEEN(low, high) (((PAGE_ALIGN_BOTTOM(high) - PAGE_ALIGN_BOTTOM(low)) / PAGE_SIZE) + 1) 13 | 14 | void set_mem_addr_rw(const unsigned long vaddr, unsigned long len) 15 | { 16 | unsigned long addr = PAGE_ALIGN_BOTTOM(vaddr); 17 | pr_loc_dbg("Disabling memory protection for page(s) at %p+%lu/%u (<<%p)", (void *) vaddr, len, 18 | (unsigned int) NUM_PAGES_BETWEEN(vaddr, vaddr + len), (void *) addr); 19 | 20 | //theoretically this should use set_pte_atomic() but we're touching pages that will not be modified by anything else 21 | unsigned int level; 22 | for(; addr <= vaddr; addr += PAGE_SIZE) { 23 | pte_t *pte = lookup_address(addr, &level); 24 | pte->pte |= _PAGE_RW; 25 | } 26 | 27 | _flush_tlb_all(); 28 | } 29 | 30 | void set_mem_addr_ro(const unsigned long vaddr, unsigned long len) 31 | { 32 | unsigned long addr = PAGE_ALIGN_BOTTOM(vaddr); 33 | pr_loc_dbg("Enabling memory protection for page(s) at %p+%lu/%u (<<%p)", (void *) vaddr, len, 34 | (unsigned int) NUM_PAGES_BETWEEN(vaddr, vaddr + len), (void *) addr); 35 | 36 | //theoretically this should use set_pte_atomic() but we're touching pages that will not be modified by anything else 37 | unsigned int level; 38 | for(; addr <= vaddr; addr += PAGE_SIZE) { 39 | pte_t *pte = lookup_address(addr, &level); 40 | pte->pte &= ~_PAGE_RW; 41 | } 42 | 43 | _flush_tlb_all(); 44 | } -------------------------------------------------------------------------------- /internal/helper/memory_helper.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_MEMORY_HELPER_H 2 | #define REDPILL_MEMORY_HELPER_H 3 | 4 | #define WITH_MEM_UNLOCKED(vaddr, size, code) \ 5 | do { \ 6 | set_mem_addr_rw((unsigned long)(vaddr), size); \ 7 | ({code}); \ 8 | set_mem_addr_ro((unsigned long)(vaddr), size); \ 9 | } while(0) 10 | 11 | /** 12 | * Disables write-protection for the memory where symbol resides 13 | * 14 | * There are a million different methods of circumventing the memory protection in Linux. The reason being the kernel 15 | * people make it harder and harder to modify syscall table (& others in the process), which in general is a great idea. 16 | * There are two core methods people use: 1) disabling CR0 WP bit, and 2) setting memory page(s) as R/W. 17 | * The 1) is a flag, present on x86 CPUs, which when cleared configures the MMU to *ignore* write protection set on 18 | * memory regions. However, this flag is per-core (=synchronization problems) and it works as all-or-none. We don't 19 | * want to leave such a big thing disabled (especially for long time). 20 | * The second mechanism disabled memory protection on per-page basis. Normally the kernel contains set_memory_rw() which 21 | * does what it says - sets the address (which should be lower-page aligned) to R/W. However, this function is evil for 22 | * some time (~2.6?). In its course it calls static_protections() which REMOVES the R/W flag from the request 23 | * (effectively making the call a noop) while still returning 0. Guess how long we debugged that... additionally, that 24 | * function is removed in newer kernels. 25 | * The easiest way is to just lookup the page table entry for a given address, modify the R/W attribute directly and 26 | * dump CPU caches. This will work as there's no middle-man to mess with our request. 27 | */ 28 | void set_mem_addr_rw(const unsigned long vaddr, unsigned long len); 29 | 30 | /** 31 | * Reverses set_mem_rw() 32 | * 33 | * See set_mem_rw() for details 34 | */ 35 | void set_mem_addr_ro(const unsigned long vaddr, unsigned long len); 36 | 37 | #endif //REDPILL_MEMORY_HELPER_H 38 | -------------------------------------------------------------------------------- /internal/helper/symbol_helper.c: -------------------------------------------------------------------------------- 1 | #include "symbol_helper.h" 2 | #include //__symbol_get(), __symbol_put() 3 | #include //kallsyms_lookup_name 4 | 5 | bool kernel_has_symbol(const char *name) { 6 | if (__symbol_get(name)) { //search for public symbols 7 | __symbol_put(name); 8 | 9 | return true; 10 | } 11 | 12 | return kallsyms_lookup_name(name) != 0; 13 | } -------------------------------------------------------------------------------- /internal/helper/symbol_helper.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_SYMBOL_HELPER_H 2 | #define REDPILL_SYMBOL_HELPER_H 3 | 4 | #include //bool 5 | 6 | /** 7 | * Check if a given symbol exists 8 | * 9 | * This function will return true for both public and private kernel symbols 10 | * 11 | * @param name name of the symbol 12 | */ 13 | bool kernel_has_symbol(const char *name); 14 | 15 | #endif //REDPILL_SYMBOL_HELPER_H 16 | -------------------------------------------------------------------------------- /internal/intercept_driver_register.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_DRIVER_WATCHER_H 2 | #define REDPILL_DRIVER_WATCHER_H 3 | 4 | #include //struct device_driver, driver_find (in .c) 5 | 6 | /** 7 | * Codes which the callback call on watch can return 8 | */ 9 | typedef enum { 10 | DWATCH_NOTIFY_CONTINUE, //callback processed the data and allows for the chain to continue 11 | DWATCH_NOTIFY_DONE, //callback processed the data, allows for the chain to continue but wants to unregister 12 | DWATCH_NOTIFY_ABORT_OK, //callback processed the data and determined that fake-OK should be returned to the original caller (DWATCH_STATE_COMING only) 13 | DWATCH_NOTIFY_ABORT_BUSY, //callback processed the data and determined that fake-EBUSY should be returned to the original caller (DWATCH_STATE_COMING only) 14 | } driver_watch_notify_result; 15 | 16 | /** 17 | * Controls when the callback for loaded driver is called 18 | */ 19 | typedef enum { 20 | DWATCH_STATE_COMING = 0b100, //driver is loading, you can intercept the process using (DWATCH_NOTIFY_ABORT_*) and change data 21 | DWATCH_STATE_LIVE = 0b010, //driver just loaded 22 | } driver_watch_notify_state; 23 | 24 | typedef struct driver_watcher_instance driver_watcher_instance; 25 | typedef driver_watch_notify_result (watch_dr_callback)(struct device_driver *drv, driver_watch_notify_state event); 26 | 27 | /** 28 | * Start watching for a driver registration 29 | * 30 | * Note: if the driver is already loaded this will do nothing, unless the driver is removed and re-registers. You should 31 | * probably call is_driver_registered() first. 32 | * 33 | * @param name Name of the driver you want to observe 34 | * @param cb Callback called on an event 35 | * @param event_mask ORed driver_watch_notify_state flags to when the callback is called 36 | * 37 | * @return 0 on success, -E on error 38 | */ 39 | driver_watcher_instance *watch_driver_register(const char *name, watch_dr_callback *cb, int event_mask); 40 | 41 | /** 42 | * Undoes what watch_driver_register() did 43 | * 44 | * @return 0 on success, -E on error 45 | */ 46 | int unwatch_driver_register(driver_watcher_instance *instance); 47 | 48 | /** 49 | * Checks if a given driver exists 50 | * 51 | * Usually if the driver exists already it doesn't make sense to watch for it as the event will never be triggered 52 | * (unless the driver unregisters and registers again). If the bus is not specified here (NULL) a platform-driver will 53 | * be looked up (aka legacy driver). 54 | * 55 | * @return 0 if the driver is not registered, 1 if the driver is registered, -E on lookup error 56 | */ 57 | int is_driver_registered(const char *name, struct bus_type *bus); 58 | 59 | #endif //REDPILL_DRIVER_WATCHER_H 60 | -------------------------------------------------------------------------------- /internal/intercept_execve.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Submodule used to hook the execve() syscall, used by the userland to execute binaries. 3 | * 4 | * This submodule can currently block calls to specific binaries and fake a successful return of the execution. In the 5 | * future, if needed, an option to fake certain response and/or execute a different binary instead can be easily added 6 | * here. 7 | * 8 | * execve() is a rather special syscall. This submodule utilized override_symbool.c:override_syscall() to do the actual 9 | * ground work of replacing the call. However some syscalls (execve, fork, etc.) use ASM stubs with a non-GCC call 10 | * convention. Up until Linux v3.18 it wasn't a problem as long as the stub was called back. However, since v3.18 the 11 | * stub was changed in such a way that calling it using a normal convention from (i.e. from the shim here) will cause 12 | * IRET imbalance and a crash. This is worked around by skipping the whole stub and calling do_execve() with a filename 13 | * struct directly. This requires re-exported versions of these functions, so it may be marginally slower. 14 | * Because of that this trick is only utilized on Linux >v3.18 and older ones call the stub as normal. 15 | * 16 | * References: 17 | * - https://github.com/torvalds/linux/commit/b645af2d5905c4e32399005b867987919cbfc3ae 18 | * - https://my.oschina.net/macwe/blog/603583 19 | * - https://stackoverflow.com/questions/8372912/hooking-sys-execve-on-linux-3-x 20 | */ 21 | #include "intercept_execve.h" 22 | #include "../common.h" 23 | #include 24 | #include //struct filename 25 | #include "override/override_syscall.h" //SYSCALL_SHIM_DEFINE3, override_symbol 26 | #include "call_protected.h" //do_execve(), getname(), putname() 27 | 28 | #ifdef RPDBG_EXECVE 29 | #include "../debug/debug_execve.h" 30 | #endif 31 | 32 | #define MAX_INTERCEPTED_FILES 10 33 | 34 | static char * intercepted_filenames[MAX_INTERCEPTED_FILES] = { NULL }; 35 | 36 | int add_blocked_execve_filename(const char *filename) 37 | { 38 | if (unlikely(strlen(filename) > PATH_MAX)) 39 | return -ENAMETOOLONG; 40 | 41 | unsigned int idx = 0; 42 | while (likely(intercepted_filenames[idx])) { //Find free spot 43 | if (unlikely(strcmp(filename, intercepted_filenames[idx]) == 0)) { //Does it exist already? 44 | pr_loc_bug("File %s was already added at %d", filename, idx); 45 | return -EEXIST; 46 | } 47 | 48 | if(unlikely(++idx >= MAX_INTERCEPTED_FILES)) { //Are we out of indexes? 49 | pr_loc_bug("Tried to add %d intercepted filename (max=%d)", idx, MAX_INTERCEPTED_FILES); 50 | return -ENOMEM; 51 | } 52 | } 53 | 54 | kmalloc_or_exit_int(intercepted_filenames[idx], strsize(filename)); 55 | strcpy(intercepted_filenames[idx], filename); //Size checked above 56 | 57 | pr_loc_inf("Filename %s will be blocked from execution", filename); 58 | return 0; 59 | } 60 | 61 | SYSCALL_SHIM_DEFINE3(execve, 62 | const char __user *, filename, 63 | const char __user *const __user *, argv, 64 | const char __user *const __user *, envp) 65 | { 66 | struct filename *path = _getname(filename); 67 | 68 | //this is essentially what do_execve() (or SYSCALL_DEFINE3 on older kernels) will do if the getname ptr is invalid 69 | if (IS_ERR(path)) 70 | return PTR_ERR(path); 71 | 72 | const char *pathname = path->name; 73 | #ifdef RPDBG_EXECVE 74 | RPDBG_print_execve_call(pathname, argv); 75 | #endif 76 | 77 | for (int i = 0; i < MAX_INTERCEPTED_FILES; i++) { 78 | if (!intercepted_filenames[i]) 79 | break; 80 | 81 | if (unlikely(strcmp(pathname, intercepted_filenames[i]) == 0)) { 82 | pr_loc_inf("Blocked %s from running", pathname); 83 | //We cannot just return 0 here - execve() *does NOT* return on success, but replaces the current process ctx 84 | do_exit(0); 85 | } 86 | } 87 | 88 | //Depending on the version of the kernel do_execve() accepts bare filename (old) or the full struct filename (newer) 89 | //Additionally in older kernels we need to take care of the path lifetime and put it back (it's automatic in newer) 90 | //See: https://github.com/torvalds/linux/commit/c4ad8f98bef77c7356aa6a9ad9188a6acc6b849d 91 | #if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0) 92 | int out = _do_execve(pathname, argv, envp); 93 | _putname(path); 94 | return out; 95 | #else 96 | return _do_execve(path, argv, envp); 97 | #endif 98 | } 99 | 100 | static override_symbol_inst *sys_execve_ovs = NULL; 101 | int register_execve_interceptor() 102 | { 103 | pr_loc_dbg("Registering execve() interceptor"); 104 | 105 | if (sys_execve_ovs) { 106 | pr_loc_bug("Called %s() while execve() interceptor is already registered", __FUNCTION__); 107 | return -EEXIST; 108 | } 109 | 110 | override_symbol_or_exit_int(sys_execve_ovs, "SyS_execve", SyS_execve_shim); 111 | 112 | pr_loc_inf("execve() interceptor registered"); 113 | return 0; 114 | } 115 | 116 | int unregister_execve_interceptor() 117 | { 118 | pr_loc_dbg("Unregistering execve() interceptor"); 119 | 120 | if (!sys_execve_ovs) { 121 | pr_loc_bug("Called %s() while execve() interceptor is not registered (yet?)", __FUNCTION__); 122 | return -ENXIO; 123 | } 124 | 125 | int out = restore_symbol(sys_execve_ovs); 126 | if (out != 0) 127 | return out; 128 | sys_execve_ovs = NULL; 129 | 130 | //Free all strings duplicated in add_blocked_execve_filename() 131 | unsigned int idx = 0; 132 | while (idx < MAX_INTERCEPTED_FILES-1 && intercepted_filenames[idx]) { 133 | kfree(intercepted_filenames[idx]); 134 | intercepted_filenames[idx] = NULL; 135 | idx++; 136 | } 137 | 138 | pr_loc_inf("execve() interceptor unregistered"); 139 | return 0; 140 | } 141 | -------------------------------------------------------------------------------- /internal/intercept_execve.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_INTERCEPT_EXECVE_H 2 | #define REDPILL_INTERCEPT_EXECVE_H 3 | 4 | //There's no remove_ as this requires rearranging the list etc and is not needed for now 5 | int add_blocked_execve_filename(const char * filename); 6 | int register_execve_interceptor(void); 7 | int unregister_execve_interceptor(void); 8 | 9 | #endif //REDPILL_INTERCEPT_EXECVE_H 10 | -------------------------------------------------------------------------------- /internal/ioscheduler_fixer.c: -------------------------------------------------------------------------------- 1 | /** 2 | * This very simple submodule which prevents kernel log from being flooded with "I/O scheduler elevator not found" 3 | * 4 | * When this shim is loaded as a I/O scheduler (to load very early) it is being set as a I/O scheduler. As we later 5 | * remove the module file the system will constantly try to load now non-existing module "elevator-iosched". By 6 | * resetting the "chosen_elevator" using the same function called by "elevator=" handler we can pretend no custom 7 | * I/O scheduler was ever set (so that the system uses default one and stops complaining) 8 | */ 9 | #include "ioscheduler_fixer.h" 10 | #include "../common.h" 11 | #include "call_protected.h" //is_system_booting(), elevator_setup() 12 | #include //system_state 13 | 14 | #define SHIM_NAME "I/O scheduler fixer" 15 | 16 | int reset_elevator(void) 17 | { 18 | if (!is_system_booting()) { 19 | pr_loc_wrn("Cannot reset I/O scheduler / elevator= set - system is past booting stage (state=%d)", 20 | system_state); 21 | return 0; //This is not an error technically speaking 22 | } 23 | 24 | pr_loc_dbg("Resetting I/O scheduler to default"); 25 | return _elevator_setup("") == 1 ? 0 : -EINVAL; 26 | } -------------------------------------------------------------------------------- /internal/ioscheduler_fixer.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_IOSCHEDULER_FIXER_H 2 | #define REDPILL_IOSCHEDULER_FIXER_H 3 | 4 | int reset_elevator(void); 5 | 6 | #endif //REDPILL_IOSCHEDULER_FIXER_H 7 | -------------------------------------------------------------------------------- /internal/notifier_base.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_NOTIFIER_BASE_H 2 | #define REDPILL_NOTIFIER_BASE_H 3 | 4 | #define notifier_reg_in() pr_loc_dbg("Registering %s notifier", NOTIFIER_NAME); 5 | #define notifier_reg_ok() pr_loc_inf("Successfully registered %s notifier", NOTIFIER_NAME); 6 | #define notifier_ureg_in() pr_loc_dbg("Unregistering %s notifier", NOTIFIER_NAME); 7 | #define notifier_ureg_ok() pr_loc_inf("Successfully unregistered %s notifier", NOTIFIER_NAME); 8 | #define notifier_sub(nb) \ 9 | pr_loc_dbg("%pF (priority=%d) subscribed to %s events", (nb)->notifier_call, (nb)->priority, NOTIFIER_NAME); 10 | #define notifier_unsub(nb) \ 11 | pr_loc_dbg("%pF (priority=%d) unsubscribed from %s events", (nb)->notifier_call, (nb)->priority, NOTIFIER_NAME); 12 | 13 | #endif //REDPILL_NOTIFIER_BASE_H 14 | -------------------------------------------------------------------------------- /internal/override/override_symbol.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILLLKM_OVERRIDE_KFUNC_H 2 | #define REDPILLLKM_OVERRIDE_KFUNC_H 3 | 4 | #include 5 | #include //PTR_ERR, IS_ERR 6 | 7 | typedef struct override_symbol_inst override_symbol_inst; 8 | 9 | /************************************************* Current interface **************************************************/ 10 | /** 11 | * Calls the original symbol, returning nothing, that was previously overridden 12 | * 13 | * @param sym pointer to a override_symbol_inst 14 | * @param ... any arguments to the original function 15 | * 16 | * @return 0 if the execution succeeded, -E if it didn't 17 | */ 18 | #define call_overridden_symbol_void(sym, ...) ({ \ 19 | int __ret; \ 20 | bool __was_installed = symbol_is_overridden(sym); \ 21 | _Pragma("GCC diagnostic push") \ 22 | _Pragma("GCC diagnostic ignored \"-Wstrict-prototypes\"") \ 23 | void (*__ptr)() = __get_org_ptr(sym); \ 24 | _Pragma("GCC diagnostic pop") \ 25 | __ret = __disable_symbol_override(sym); \ 26 | if (likely(__ret == 0)) { \ 27 | __ptr(__VA_ARGS__); \ 28 | if (likely(__was_installed)) { \ 29 | __ret = __enable_symbol_override(sym); \ 30 | } \ 31 | } \ 32 | __ret; \ 33 | }); 34 | 35 | /** 36 | * Calls the original symbol, returning a value, that was previously overridden 37 | * 38 | * @param out_var name of the variable where original function return value should be placed 39 | * @param sym pointer to a override_symbol_inst 40 | * @param ... any arguments to the original function 41 | * 42 | * @return 0 if the execution succeeded, -E if it didn't 43 | */ 44 | #define call_overridden_symbol(out_var, sym, ...) ({ \ 45 | int __ret; \ 46 | bool __was_installed = symbol_is_overridden(sym); \ 47 | _Pragma("GCC diagnostic push") \ 48 | _Pragma("GCC diagnostic ignored \"-Wstrict-prototypes\"") \ 49 | typeof (out_var) (*__ptr)() = __get_org_ptr(sym); \ 50 | _Pragma("GCC diagnostic pop") \ 51 | __ret = __disable_symbol_override(sym); \ 52 | if (likely(__ret == 0)) { \ 53 | out_var = __ptr(__VA_ARGS__); \ 54 | if (likely(__was_installed)) { \ 55 | __ret = __enable_symbol_override(sym); \ 56 | } \ 57 | } \ 58 | __ret; \ 59 | }); 60 | 61 | /** 62 | * override_symbol() with automatic error handling. See the original function for details. 63 | * 64 | * @param ptr_var Variable to store ovs pointer 65 | */ 66 | #define override_symbol_or_exit_int(ptr_var, name, new_sym_ptr) \ 67 | (ptr_var) = override_symbol(name, new_sym_ptr); \ 68 | if (unlikely(IS_ERR((ptr_var)))) { \ 69 | int _err = PTR_ERR((ptr_var)); \ 70 | pr_loc_err("Failed to override %s - error=%d", name, _err); \ 71 | (ptr_var) = NULL; \ 72 | return _err; \ 73 | } \ 74 | 75 | /** 76 | * Overrides a kernel symbol with something else of your choice 77 | * 78 | * @param name Name of the kernel symbol (function) to override 79 | * @param new_sym_ptr An address/pointer to a new function 80 | * 81 | * @return Instance of override_symbol_inst struct pointer on success, ERR_PTR(-E) on error 82 | * 83 | * @example 84 | * struct override_symbol_inst *ovi; 85 | * int null_printk() { 86 | * int print_res; 87 | * call_overridden_symbol(print_res, ovi, "No print for you!"); 88 | * return print_res; 89 | * } 90 | * ovi = override_symbol("printk", null_printk); 91 | * if (IS_ERR(ovi)) { ... handle error ... } 92 | * ... 93 | * restore_symbol(backup_addr, backup_code); //restore backed-up copy of printk() 94 | * 95 | * @todo: This should be rewritten using INSN without inline ASM wizardy, but this is much more complex 96 | */ 97 | struct override_symbol_inst* __must_check override_symbol(const char *name, const void *new_sym_ptr); 98 | 99 | /** 100 | * Restores symbol overridden by override_symbol() 101 | * 102 | * For details see override_symbol() docblock 103 | * 104 | * @return 0 on success, -E on error 105 | */ 106 | int restore_symbol(struct override_symbol_inst *sym); 107 | 108 | /** 109 | * Frees the symbol previously reserved by get_ov_symbol_instance() (e.g. via override_symbol) 110 | * 111 | * !! READ THIS SERIOUS WARNING BELOW CAREFULLY !! 112 | * STOP! DO NOT USE THIS if you don't understand what it does and why it exists. This function should be called from 113 | * outside of this submodule ONLY if the overridden code disappeared from memory. This practically can happen only when 114 | * you override a symbol inside of a loadable module and the module is unloaded. In ANY other case you must call 115 | * restore_symbol() to actually restore the original code. This function simply "forgets" about the override and frees 116 | * memory (as if external module has been unloaded we are NOT allowed to touch that memory anymore as it may be freed). 117 | * It is explicitly NOT necessary to call this function after restore_symbol() as it does so internally. 118 | */ 119 | void put_overridden_symbol(struct override_symbol_inst *sym); 120 | 121 | /** 122 | * Check if the given symbol override is currently active 123 | */ 124 | bool symbol_is_overridden(struct override_symbol_inst *sym); 125 | 126 | 127 | /****************** Private helpers (should not be used directly by any code outside of this unit!) *******************/ 128 | #include 129 | int __enable_symbol_override(override_symbol_inst *sym); 130 | int __disable_symbol_override(override_symbol_inst *sym); 131 | void * __get_org_ptr(struct override_symbol_inst *sym); 132 | 133 | #endif //REDPILLLKM_OVERRIDE_KFUNC_H 134 | -------------------------------------------------------------------------------- /internal/override/override_syscall.c: -------------------------------------------------------------------------------- 1 | #include "override_syscall.h" 2 | #include "../../common.h" 3 | #include "../helper/memory_helper.h" //set_mem_addr_ro(), set_mem_addr_rw() 4 | #include //__NR_syscall_max & NR_syscalls 5 | #include //syscalls numbers (e.g. __NR_read) 6 | 7 | static unsigned long *syscall_table_ptr = NULL; 8 | static void print_syscall_table(unsigned int from, unsigned to) 9 | { 10 | if (unlikely(!syscall_table_ptr)) { 11 | pr_loc_dbg("Cannot print - no syscall_table_ptr address"); 12 | return; 13 | } 14 | 15 | if (unlikely(from < 0 || to > __NR_syscall_max || from > to)) { 16 | pr_loc_bug("%s called with from=%d to=%d which are invalid", __FUNCTION__, from, to); 17 | return; 18 | } 19 | 20 | pr_loc_dbg("Printing syscall table %d-%d @ %p containing %d elements", from, to, (void *)syscall_table_ptr, NR_syscalls); 21 | for (unsigned int i = from; i < to; i++) { 22 | pr_loc_dbg("#%03d\t%pS", i, (void *)syscall_table_ptr[i]); 23 | } 24 | } 25 | 26 | static int find_sys_call_table(void) 27 | { 28 | syscall_table_ptr = (unsigned long *)kallsyms_lookup_name("sys_call_table"); 29 | if (syscall_table_ptr != 0) { 30 | pr_loc_dbg("Found sys_call_table @ <%p> using kallsyms", syscall_table_ptr); 31 | return 0; 32 | } 33 | 34 | //See https://kernelnewbies.kernelnewbies.narkive.com/L1uH0n8P/ 35 | //In essence some systems will have it and some will not - finding it using kallsyms is the easiest and fastest 36 | pr_loc_dbg("Failed to locate vaddr for sys_call_table using kallsyms - falling back to memory search"); 37 | 38 | /* 39 | There's also the bruteforce way - scan through the memory until you find it :D 40 | We know numbers for syscalls (e.g. __NR_close, __NR_write, __NR_read, etc.) which are essentially fixed positions 41 | in the sys_call_table. We also know addresses of functions handling these calls (sys_close/sys_write/sys_read 42 | etc.). This lets us scan the memory for one syscall address reference and when found confirm if this is really 43 | a place of sys_call_table by verifying other 2-3 places to make sure other syscalls are where they should be 44 | The huge downside of this method is it is slow as potentially the amount of memory to search may be large. 45 | */ 46 | unsigned long sys_close_ptr = kallsyms_lookup_name("sys_close"); 47 | unsigned long sys_open_ptr = kallsyms_lookup_name("sys_open"); 48 | unsigned long sys_read_ptr = kallsyms_lookup_name("sys_read"); 49 | unsigned long sys_write_ptr = kallsyms_lookup_name("sys_write"); 50 | if (sys_close_ptr == 0 || sys_open_ptr == 0 || sys_read_ptr == 0 || sys_write_ptr == 0) { 51 | pr_loc_bug( 52 | "One or more syscall handler addresses cannot be located: " 53 | "sys_close<%p>, sys_open<%p>, sys_read<%p>, sys_write<%p>", 54 | (void *)sys_close_ptr, (void *)sys_open_ptr, (void *)sys_read_ptr, (void *)sys_write_ptr); 55 | return -EFAULT; 56 | } 57 | 58 | /* 59 | To speed up things a bit we search from a known syscall which was loaded early into the memory. To be safe we pick 60 | the earliest address and go from there. It can be nicely visualized on a system which DO export sys_call_table 61 | by running grep -E ' (__x64_)?sys_(close|open|read|write|call_table)$' /proc/kallsyms | sort 62 | You will get something like that: 63 | ffffffff860c18b0 T __x64_sys_close 64 | ffffffff860c37a0 T __x64_sys_open 65 | ffffffff860c7a80 T __x64_sys_read 66 | ffffffff860c7ba0 T __x64_sys_write 67 | ffffffff86e013a0 R sys_call_table <= it's way below any of the syscalls but not too far (~13,892,336 bytes) 68 | */ 69 | unsigned long i = sys_close_ptr; 70 | if (sys_open_ptr < i) i = sys_open_ptr; 71 | if (sys_read_ptr < i) i = sys_read_ptr; 72 | if (sys_write_ptr < i) i = sys_write_ptr; 73 | 74 | //If everything goes well it should take ~1-2ms tops (which is slow in the kernel sense but it's not bad) 75 | pr_loc_dbg("Scanning memory for sys_call_table starting at %p", (void *)i); 76 | for (; i < ULONG_MAX; i += sizeof(void *)) { 77 | syscall_table_ptr = (unsigned long *)i; 78 | 79 | if (unlikely( 80 | syscall_table_ptr[__NR_close] == sys_close_ptr && 81 | syscall_table_ptr[__NR_open] == sys_open_ptr && 82 | syscall_table_ptr[__NR_read] == sys_read_ptr && 83 | syscall_table_ptr[__NR_write] == sys_write_ptr 84 | )) { 85 | pr_loc_dbg("Found sys_call_table @ %p", (void *)syscall_table_ptr); 86 | return 0; 87 | } 88 | } 89 | 90 | pr_loc_bug("Failed to find sys call table"); 91 | syscall_table_ptr = NULL; 92 | return -EFAULT; 93 | } 94 | 95 | static unsigned long *overridden_syscall[NR_syscalls] = { NULL }; //@todo this should be alloced dynamically 96 | int override_syscall(unsigned int syscall_num, const void *new_sysc_ptr, void * *org_sysc_ptr) 97 | { 98 | pr_loc_dbg("Overriding syscall #%d with %pf()<%p>", syscall_num, new_sysc_ptr, new_sysc_ptr); 99 | 100 | int out = 0; 101 | if (unlikely(!syscall_table_ptr)) { 102 | out = find_sys_call_table(); 103 | if (unlikely(out != 0)) 104 | return out; 105 | } 106 | 107 | if (unlikely(syscall_num > __NR_syscall_max)) { 108 | pr_loc_bug("Invalid syscall number: %d > %d", syscall_num, __NR_syscall_max); 109 | return -EINVAL; 110 | } 111 | 112 | print_syscall_table(syscall_num-5, syscall_num+5); 113 | 114 | if (unlikely(overridden_syscall[syscall_num])) { 115 | pr_loc_bug("Syscall %d is already overridden - will be replaced (bug?)", syscall_num); 116 | } else { 117 | //Only save original-original entry (not the override one) 118 | overridden_syscall[syscall_num] = (unsigned long *)syscall_table_ptr[syscall_num]; 119 | } 120 | 121 | if (org_sysc_ptr != 0) 122 | *org_sysc_ptr = overridden_syscall[syscall_num]; 123 | 124 | set_mem_addr_rw((long)&syscall_table_ptr[syscall_num], sizeof(unsigned long)); 125 | pr_loc_dbg("syscall #%d originally %ps<%p> will now be %ps<%p> @ %d", syscall_num, 126 | (void *) overridden_syscall[syscall_num], (void *) overridden_syscall[syscall_num], new_sysc_ptr, 127 | new_sysc_ptr, smp_processor_id()); 128 | syscall_table_ptr[syscall_num] = (unsigned long) new_sysc_ptr; 129 | set_mem_addr_ro((long)&syscall_table_ptr[syscall_num], sizeof(unsigned long)); 130 | 131 | print_syscall_table(syscall_num-5, syscall_num+5); 132 | 133 | return out; 134 | } 135 | 136 | int restore_syscall(unsigned int syscall_num) 137 | { 138 | pr_loc_dbg("Restoring syscall #%d", syscall_num); 139 | 140 | if (unlikely(!syscall_table_ptr)) { 141 | pr_loc_bug("Syscall table not found in %s ?!", __FUNCTION__); 142 | return -EFAULT; 143 | } 144 | 145 | if (unlikely(syscall_num > __NR_syscall_max)) { 146 | pr_loc_bug("Invalid syscall number: %d > %d", syscall_num, __NR_syscall_max); 147 | return -EINVAL; 148 | } 149 | 150 | if (unlikely(overridden_syscall[syscall_num] == 0)) { 151 | pr_loc_bug("Syscall #%d cannot be restored - it was never overridden", syscall_num); 152 | return -EINVAL; 153 | } 154 | 155 | print_syscall_table(syscall_num-5, syscall_num+5); 156 | 157 | set_mem_addr_rw((long)&syscall_table_ptr[syscall_num], sizeof(unsigned long)); 158 | pr_loc_dbg("Restoring syscall #%d from %ps<%p> to original %ps<%p>", syscall_num, 159 | (void *) syscall_table_ptr[syscall_num], (void *) syscall_table_ptr[syscall_num], 160 | (void *) overridden_syscall[syscall_num], (void *) overridden_syscall[syscall_num]); 161 | syscall_table_ptr[syscall_num] = (unsigned long)overridden_syscall[syscall_num]; 162 | set_mem_addr_rw((long)&syscall_table_ptr[syscall_num], sizeof(unsigned long)); 163 | 164 | print_syscall_table(syscall_num-5, syscall_num+5); 165 | 166 | return 0; 167 | } -------------------------------------------------------------------------------- /internal/override/override_syscall.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_OVERRIDE_SYSCALL_H 2 | #define REDPILL_OVERRIDE_SYSCALL_H 3 | 4 | #include "override_symbol.h" 5 | #include 6 | 7 | //Modified syscall defines for shims based on native Linux syscalls (defined in linux/syscalls.h) 8 | #define SYSCALL_SHIM_DEFINE1(name, ...) SYSCALL_SHIM_DEFINEx(1, _##name##_shim, __VA_ARGS__) 9 | #define SYSCALL_SHIM_DEFINE2(name, ...) SYSCALL_SHIM_DEFINEx(2, _##name##_shim, __VA_ARGS__) 10 | #define SYSCALL_SHIM_DEFINE3(name, ...) SYSCALL_SHIM_DEFINEx(3, _##name##_shim, __VA_ARGS__) 11 | #define SYSCALL_SHIM_DEFINE4(name, ...) SYSCALL_SHIM_DEFINEx(4, _##name##_shim, __VA_ARGS__) 12 | #define SYSCALL_SHIM_DEFINE5(name, ...) SYSCALL_SHIM_DEFINEx(5, _##name##_shim, __VA_ARGS__) 13 | #define SYSCALL_SHIM_DEFINE6(name, ...) SYSCALL_SHIM_DEFINEx(6, _##name##_shim, __VA_ARGS__) 14 | #define SYSCALL_SHIM_DEFINEx(x, name, ...) \ 15 | static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \ 16 | static asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \ 17 | { \ 18 | long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__)); \ 19 | __MAP(x,__SC_TEST,__VA_ARGS__); \ 20 | __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \ 21 | return ret; \ 22 | } \ 23 | static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)) 24 | 25 | /** 26 | * Non-destructively overrides a syscall 27 | * 28 | * This produces an effect similar to override_symbol(). However, it should be faster, safer, and most importantly 29 | * allows calling the original syscall in the override. 30 | * 31 | * Warning: DO NOT use this method to override stubbed syscalls. These are syscall which aren't named "sys_foo" (e.g. 32 | * sys_execve or SyS_execve [alias]) but are handled by ASM stubs in arch/x86/kernel/entry_64.S (and visible as e.g. 33 | * stub_execve). If you do override such call with a normal function in the syscall table things will start breaking 34 | * unexpectedly as the registries will be modified in an unexpected way (stubs don't use cdecl)! 35 | * In such cases you need to override the actual sys_* (or even better: SyS_*) function with a jump using 36 | * override_symbol(). Either way you should use SYSCALL_SHIM_DEFINE#() to define the new target/shim. 37 | * Make sure to read https://lwn.net/Articles/604287/ and https://lwn.net/Articles/604406/ 38 | * 39 | * @param syscall_num Number of the syscall to override (e.g. open) 40 | * You can find them as __NR_* defines in arch/x86/include/generated/uapi/asm/unistd_64.h 41 | * @param new_sysc_ptr An address/pointer to a new function 42 | * @param org_sysc_ptr Pointer to some space to save address of the original syscall (warning: it's a pointer-pointer); 43 | * You can pass a null-ptr if you don't care about the original syscall and the function will not 44 | * touch it 45 | * 46 | * @return 0 on success, -E on error 47 | */ 48 | int override_syscall(unsigned int syscall_num, const void *new_sysc_ptr, void * *org_sysc_ptr); 49 | 50 | /** 51 | * Restores the syscall previously replaced by override_syscall() 52 | * 53 | * For details see override_syscall() docblock. 54 | * 55 | * @return 0 on success, -E on error 56 | */ 57 | int restore_syscall(unsigned int syscall_num); 58 | 59 | #endif //REDPILL_OVERRIDE_SYSCALL_H 60 | -------------------------------------------------------------------------------- /internal/scsi/scsi_notifier.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_SCSI_NOTIFIER_H 2 | #define REDPILL_SCSI_NOTIFIER_H 3 | 4 | #include //All other parts including scsi_notifier.h cannot really not use linux/notifier.h 5 | 6 | typedef enum { 7 | SCSI_EVT_DEV_PROBING, //device is being probed; it can be modified or outright ignored 8 | SCSI_EVT_DEV_PROBED_OK, //device is probed and ready 9 | SCSI_EVT_DEV_PROBED_ERR, //device was probed but it failed 10 | } scsi_event; 11 | 12 | /** 13 | * Callback signature: void (*f)(struct notifier_block *self, unsigned long state, void *data), where: 14 | * unsigned long state => scsi_event event 15 | * void *data => struct scsi_device *sdp 16 | * 17 | * Currently these methods are DELIBERATELY limited to SCSI TYPE_DISK scope. If you need other SCSI devices watching 18 | * add another set of methods (subscribe scsi_device_events() and such, do NOT extend the scope of these methods as 19 | * other parts of the code rely on pre-filtered events as in most cases listening for ALL devices is a lot of noise). 20 | * 21 | * @return 22 | */ 23 | int subscribe_scsi_disk_events(struct notifier_block *nb); 24 | int unsubscribe_scsi_disk_events(struct notifier_block *nb); 25 | 26 | int register_scsi_notifier(void); 27 | int unregister_scsi_notifier(void); 28 | 29 | #endif //REDPILL_SCSI_NOTIFIER_H 30 | -------------------------------------------------------------------------------- /internal/scsi/scsi_notifier_list.c: -------------------------------------------------------------------------------- 1 | #include "scsi_notifier_list.h" 2 | #include 3 | 4 | BLOCKING_NOTIFIER_HEAD(rp_scsi_notify_list); 5 | -------------------------------------------------------------------------------- /internal/scsi/scsi_notifier_list.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This file exists solely as a workaround for GCC bug #275674 - static structures are misdirected as dynamic 3 | * 4 | * Linux contains many clever idioms. One of them is a complex initialization of heads for notifier chains 5 | * (include/linux/notifier.h). They do contain an embedded cast to a struct. GCC <5 detects that as a dynamic allocation 6 | * and refuses to initialize it statically. This breaks all the macros for notifier (e.g. BLOCKING_NOTIFIER_INIT). Old 7 | * kernels (i.e. <3.18) cannot be compiled with GCC >4.9 so... we cannot use a newer GCC but we cannot use older due to 8 | * a bug. One of the solutions would be to convert the whole code of this module to GNU89 but this is painful to use. 9 | * 10 | * Such structures are working in GNU89 mode as well as when defined as a heap variable in a function. However, GCC is 11 | * smart enough to release the memory from within a function (so we cannot just wrap it in a function and return a ptr). 12 | * Due to the complex nature of the struct we didn't want to hardcode it here as they change between kernel version. 13 | * As a workaround we created a separate compilation unit containing just the struct and compile it in GNU89 mode, while 14 | * rest of the project stays at GNU99. 15 | * 16 | * Resources 17 | * - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63567 (bug report) 18 | * - https://unix.stackexchange.com/a/275674 (kernel v3.18 restriction) 19 | * - https://stackoverflow.com/a/49119902 (linking files compiled with different language standard in GCC) 20 | * - https://www.kernel.org/doc/Documentation/kbuild/makefiles.txt (compilation option per file in Kbuild; sect. 3.7) 21 | */ 22 | #ifndef REDPILL_SCSI_NOTIFIER_LIST_H 23 | #define REDPILL_SCSI_NOTIFIER_LIST_H 24 | 25 | extern struct blocking_notifier_head rp_scsi_notify_list; 26 | 27 | #endif //REDPILL_SCSI_NOTIFIER_LIST_H 28 | -------------------------------------------------------------------------------- /internal/scsi/scsi_toolbox.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_SCSI_TOOLBOX_H 2 | #define REDPILL_SCSI_TOOLBOX_H 3 | 4 | #include //bool 5 | 6 | typedef struct device device; 7 | typedef struct scsi_device scsi_device; 8 | typedef int (on_scsi_device_cb)(struct scsi_device *sdp); 9 | 10 | #define SCSI_DRV_NAME "sd" //useful for triggering watchers 11 | //To use this one import intercept_driver_register.h header (it's not imported here to avoid pollution) 12 | #define watch_scsi_driver_register(callback, event_mask) \ 13 | watch_driver_register(SCSI_DRV_NAME, (callback), (event_mask)) 14 | 15 | #define IS_SCSI_DRIVER_ERROR(state) (unlikely((state) < 0)) 16 | typedef enum { 17 | SCSI_DRV_NOT_LOADED = 0, 18 | SCSI_DRV_LOADED = 1, 19 | } scsi_driver_state; 20 | 21 | /** 22 | * From the kernel's pov SCSI devices include SCSI hosts, "leaf" devices, and others - this filters real SCSI devices 23 | * 24 | * This is simply an alias for scsi_is_sdev_device() which is more descriptive for people who aren't SCSI wizards. 25 | * 26 | * @param dev struct device* 27 | */ 28 | #define is_scsi_leaf(dev) scsi_is_sdev_device(dev) 29 | 30 | /** 31 | * Attempts to read capacity of a device assuming reasonably modern pathway 32 | * 33 | * This function (along with scsi_read_cap{10|16}) is loosely based on drivers/scsi/sd.c:sd_read_capacity(). However, 34 | * this method cuts some corners to be faster as we're expecting rather modern hardware. Additionally, functions from 35 | * sd.c cannot be used as they're static. Even that some of them can be called using kallsyms they aren't stateless and 36 | * will cause a KP later on (as they modify the device passed to them). 37 | * Thus this function should be seen as a way to quickly estimate (as it reports full mebibytes rounded down) the 38 | * capacity without causing side effects. 39 | * 40 | * @param sdp 41 | * @return capacity in full mebibytes, or -E on error 42 | */ 43 | long long opportunistic_read_capacity(struct scsi_device *sdp); 44 | 45 | /** 46 | * Checks if a SCSI device is a SCSI-complain disk (e.g. SATA, SAS, iSCSI etc) 47 | * 48 | * To be 101% sure and proper you should probably call is_scsi_leaf() first 49 | */ 50 | bool is_scsi_disk(struct scsi_device *sdp); 51 | 52 | /** 53 | * Checks if a given generic device is an SCSI disk connected to a SATA port/host controller 54 | * 55 | * Every SATA disk, by definition, will also be an SCSI disk (as SATA is a connector carrying SCSI commands) 56 | */ 57 | bool is_sata_disk(struct device *dev); 58 | 59 | /** 60 | * Triggers a re-probe of SCSI leaf device by forcefully "unplugging" and "replugging" the device 61 | * 62 | * WARNING: be careful what are you doing - this method is no different than yanking a power cable from a device, so if 63 | * you do that with a disk which is used data loss may occur! 64 | * 65 | * @return 0 on success, -E on error 66 | */ 67 | int scsi_force_replug(scsi_device *sdp); 68 | 69 | /** 70 | * Locates & returns SCSI driver structure if loaded 71 | * 72 | * @return driver struct on success, NULL if driver is not loaded, ERR_PTR(-E) on error 73 | */ 74 | struct device_driver *find_scsi_driver(void); 75 | 76 | /** 77 | * Checks if SCSI driver is loaded or not 78 | * 79 | * This function is useful to make a decision whether to just watch for new devices or watch for new ones + scan 80 | * existing ones. You cannot just scan blindly as this will cause an error. 81 | * 82 | * @return 0 if not loaded, 1 if loaded, -E on error; see scsi_driver_state enum for constants 83 | */ 84 | int is_scsi_driver_loaded(void); 85 | 86 | /** 87 | * Traverses list of all SCSI devices and calls the callback with every leaf/terminal device found 88 | * 89 | * @return 0 on success, -E on failure. -ENXIO is reserved to always mean that the driver is not loaded 90 | */ 91 | int for_each_scsi_leaf(on_scsi_device_cb *cb); 92 | 93 | /** 94 | * Traverses list of all SCSI devices and calls the callback with every SCSCI-complaint disk found 95 | * 96 | * @return 0 on success, -E on failure. -ENXIO is reserved to always mean that the driver is not loaded 97 | */ 98 | int for_each_scsi_disk(on_scsi_device_cb *cb); 99 | 100 | #endif //REDPILL_SCSI_TOOLBOX_H 101 | -------------------------------------------------------------------------------- /internal/scsi/scsiparam.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains a list of cherry-picked constants useful while dealing with SCSI subsystem 3 | */ 4 | #ifndef REDPILL_SCSIPARAM_H 5 | #define REDPILL_SCSIPARAM_H 6 | 7 | #include //KERNEL_VERSION_CODE, KERNEL_VERSION() 8 | #include //SERVICE_ACTION_IN or SERVICE_ACTION_IN_16 9 | 10 | #define SCSI_RC16_LEN 32 //originally defined in drivers/scsi/sd.c as RC16_LEN 11 | #define SCSI_CMD_TIMEOUT (30 * HZ) //originally defined in drivers/scsi/sd.h as SD_TIMEOUT 12 | #define SCSI_CMD_MAX_RETRIES 5 //normal drives shouldn't fail the command even once 13 | #define SCSI_CAP_MAX_RETRIES 3 14 | #define SCSI_BUF_SIZE 512 //originally defined in drivers/scsi/sd.h as SD_BUF_SIZE 15 | 16 | //Old kernels used ambiguous constant: https://github.com/torvalds/linux/commit/eb846d9f147455e4e5e1863bfb5e31974bb69b7c 17 | #if LINUX_VERSION_CODE < KERNEL_VERSION(3,19,0) 18 | #define SCSI_SERVICE_ACTION_IN_16 SERVICE_ACTION_IN 19 | #else 20 | #define SCSI_SERVICE_ACTION_IN_16 SERVICE_ACTION_IN_16 21 | #endif 22 | 23 | 24 | #endif //REDPILL_SCSIPARAM_H 25 | -------------------------------------------------------------------------------- /internal/stealth.c: -------------------------------------------------------------------------------- 1 | #include "stealth.h" 2 | #include "stealth/sanitize_cmdline.h" 3 | #include //struct module (for list_del) 4 | 5 | //TODO: 6 | //https://github.com/xcellerator/linux_kernel_hacking/blob/master/3_RootkitTechniques/3.0_hiding_lkm/rootkit.c 7 | //remove file which was used for insmod 8 | //remove kernel taint 9 | //remove module loading from klog 10 | //delete module file from ramdisk 11 | 12 | int initialize_stealth(void *config2) 13 | { 14 | struct runtime_config *config = config2; 15 | 16 | int error = 0; 17 | #if STEALTH_MODE <= STEALTH_MODE_OFF 18 | //STEALTH_MODE_OFF shortcut 19 | return error; 20 | #endif 21 | 22 | #if STEALTH_MODE > STEALTH_MODE_OFF 23 | //These are STEALTH_MODE_BASIC ones 24 | if ((error = register_stealth_sanitize_cmdline(config->cmdline_blacklist)) != 0) 25 | return error; 26 | #endif 27 | 28 | #if STEALTH_MODE > STEALTH_MODE_BASIC 29 | //These will be STEALTH_MODE_NORMAL ones 30 | #endif 31 | 32 | #if STEALTH_MODE > STEALTH_MODE_NORMAL 33 | //These will be STEALTH_MODE_FULL ones 34 | list_del(&THIS_MODULE->list); 35 | #endif 36 | 37 | return error; 38 | } 39 | 40 | int uninitialize_stealth(void) 41 | { 42 | int error; 43 | 44 | #if STEALTH_MODE > STEALTH_MODE_NORMAL 45 | //These will be STEALTH_MODE_FULL ones 46 | #endif 47 | 48 | #if STEALTH_MODE > STEALTH_MODE_BASIC 49 | //These will be STEALTH_MODE_NORMAL ones 50 | #endif 51 | 52 | #if STEALTH_MODE > STEALTH_MODE_OFF 53 | //These are STEALTH_MODE_BASIC ones 54 | if ((error = unregister_stealth_sanitize_cmdline()) != 0) 55 | return error; 56 | #endif 57 | 58 | //Mode set to STEALTH_MODE_OFF or nothing failed before 59 | return error; 60 | } -------------------------------------------------------------------------------- /internal/stealth.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This header file should be included as the first one before anything else so other header files can use STEALTH_MODE 3 | */ 4 | 5 | #ifndef REDPILLLKM_STEALTH_H 6 | #define REDPILLLKM_STEALTH_H 7 | 8 | #define STEALTH_MODE_OFF 0 //Nothing is hidden, useful for full-on debugging 9 | #define STEALTH_MODE_BASIC 1 //Hides basic things like cmdline (which is more to prevent DSM code from complaining about unknown options etc.) 10 | #define STEALTH_MODE_NORMAL 2 //Hides everything except making the module not unloadable 11 | #define STEALTH_MODE_FULL 3 //Same as STEALTH_MODE_NORMAL + removes the module from list of loaded modules & all logs 12 | 13 | //Define just after levels so other headers can use it if needed to e.g. replace some macros 14 | #ifndef STEALTH_MODE 15 | //#warning "Stealth mode not specified - using default" 16 | #define STEALTH_MODE STEALTH_MODE_BASIC 17 | #endif 18 | 19 | //Some compile-time stealthiness 20 | #if STEALTH_MODE > STEALTH_MODE_OFF //STEALTH_MODE_BASIC or above 21 | #define VIRTUAL_UART_THREAD_FMT "irq/%d-serial" //pattern format for vUART kernel thread which spoofs IRQ one 22 | #endif 23 | 24 | struct runtime_config; 25 | 26 | int initialize_stealth(void *config); 27 | int uninitialize_stealth(void); 28 | 29 | #endif //REDPILLLKM_STEALTH_H -------------------------------------------------------------------------------- /internal/stealth/sanitize_cmdline.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This submodule removes blacklisted entries from /proc/cmdline to hide some options after the LKM is loaded 3 | * 4 | * OVERVIEW 5 | * The main reason to sanitize cmdline is to avoid leaking information like "vid=..." or "pid=..." to userspace. Doing 6 | * this may cause some other external modules parsing kernel cmdline to be confused why such options are present in the 7 | * boot params. 8 | * 9 | * HOW IT WORKS? 10 | * The module overrides cmdline_proc_show() from fs/proc/cmdline.c with a jump to our implementation. The implementation 11 | * here serves a filtrated version of the cmdline. 12 | * 13 | * WHY OVERRIDE A STATIC METHOD? 14 | * This module has actually been rewritten to hard-override cmdline_proc_show() instead of "gently" finding the dentry 15 | * for /proc/cmdline, and then without modifying the dentry replacing the read operation in file_operations struct. 16 | * While this method is much cleaner and less invasive it has two problems: 17 | * - Requires "struct proc_dir_entry" (which is internal and thus not available in toolkit builds) 18 | * - Doesn't work if the module is loaded as ioscheduler (as funny enough this code will execute BEFORE /proc/cmdline 19 | * is created) 20 | * This change has been made in commit "Rewrite cmdline sanitize to replace cmdline_proc_show". 21 | * 22 | * FILTRATION 23 | * The second part of the code deals with the actual filtration. List of blacklisted entries is passed during 24 | * registration (to allow flexibility). Usually it will be gathered form pre-generated config. Then a filtrated copy of 25 | * cmdline is created once (as this is a quite expensive string-ridden operation). 26 | * The only sort-of way to find the original implementation is to access the kmesg buffer where the original cmdline is 27 | * baked into early on boot. Technically we can replace that too but this will get veeery messy and I doubt anyone will 28 | * try dig through kmesg messages with a regex for cmdline (especially that with a small dmesg buffer it will roll over) 29 | */ 30 | 31 | #include "sanitize_cmdline.h" 32 | #include "../../common.h" 33 | #include "../../config/cmdline_delegate.h" //get_kernel_cmdline() & CMDLINE_MAX 34 | #include "../override/override_symbol.h" //override_symbol() & restore_symbol() 35 | #include //seq_file, seq_printf() 36 | 37 | /** 38 | * Pre-generated filtered cmdline (by default it's an empty string in case it's somehow printed before filtration) 39 | * See filtrate_cmdline() for details 40 | */ 41 | static char *filtrated_cmdline = NULL; 42 | 43 | /** 44 | * Check if a given cmdline token is on the blacklist 45 | */ 46 | static bool 47 | is_token_blacklisted(const char *param_pointer, cmdline_token *cmdline_blacklist[MAX_BLACKLISTED_CMDLINE_TOKENS]) { 48 | for (int i = 0; i < MAX_BLACKLISTED_CMDLINE_TOKENS; i++) { 49 | if (!cmdline_blacklist[i]) 50 | return false; 51 | 52 | if (strncmp(param_pointer, (char *)cmdline_blacklist[i], strlen((char *)cmdline_blacklist[i])) == 0) 53 | return true; 54 | } 55 | 56 | return false; 57 | } 58 | 59 | /** 60 | * Filters-out all blacklisted entries from the cmdline string (fetched from /proc/cmdline) 61 | */ 62 | static int filtrate_cmdline(cmdline_token *cmdline_blacklist[MAX_BLACKLISTED_CMDLINE_TOKENS]) 63 | { 64 | char *raw_cmdline; 65 | kmalloc_or_exit_int(raw_cmdline, strlen_to_size(CMDLINE_MAX)); 66 | 67 | long cmdline_len = get_kernel_cmdline(raw_cmdline, CMDLINE_MAX); 68 | if(unlikely(cmdline_len < 0)) { //if <0 it's an error code 69 | pr_loc_dbg("get_kernel_cmdline failed with %ld", cmdline_len); 70 | kfree(raw_cmdline); 71 | return (int) cmdline_len; 72 | } 73 | 74 | filtrated_cmdline = kmalloc(strlen_to_size(cmdline_len), GFP_KERNEL); 75 | if (unlikely(!filtrated_cmdline)) { 76 | kfree(raw_cmdline); 77 | kalloc_error_int(filtrated_cmdline, strlen_to_size(cmdline_len)); 78 | } 79 | 80 | char *single_param_chunk; //Pointer to the beginning of the cmdline token 81 | char *filtrated_ptr = &filtrated_cmdline[0]; //Pointer to the current position in filtered 82 | 83 | size_t curr_param_len; 84 | while ((single_param_chunk = strsep(&raw_cmdline, CMDLINE_SEP)) != NULL) { 85 | if (single_param_chunk[0] == '\0') //Skip empty 86 | continue; 87 | 88 | if (is_token_blacklisted(single_param_chunk, cmdline_blacklist)) { 89 | pr_loc_dbg("Cmdline param \"%s\" blacklisted - skipping", single_param_chunk); 90 | continue; 91 | } 92 | 93 | curr_param_len = strlen(single_param_chunk); 94 | memcpy(filtrated_ptr, single_param_chunk, curr_param_len); 95 | filtrated_ptr += curr_param_len; 96 | *(filtrated_ptr++) = ' '; 97 | } 98 | 99 | *(filtrated_ptr-1) = '\0'; //Terminate whole param string (removing the trailing space) 100 | kfree(raw_cmdline); 101 | 102 | pr_loc_dbg("Sanitized cmdline to: %s", filtrated_cmdline); 103 | 104 | return 0; 105 | } 106 | 107 | /** 108 | * Handles fs/proc/ semantics for reading. See include/linux/fs.h:file_operations.read for details. 109 | */ 110 | static int cmdline_proc_show_filtered(struct seq_file *m, void *v) 111 | { 112 | seq_printf(m, "%s\n", filtrated_cmdline); 113 | return 0; 114 | } 115 | 116 | static override_symbol_inst *ov_cmdline_proc_show = NULL; 117 | int register_stealth_sanitize_cmdline(cmdline_token *cmdline_blacklist[MAX_BLACKLISTED_CMDLINE_TOKENS]) 118 | { 119 | if (unlikely(ov_cmdline_proc_show)) { 120 | pr_loc_bug("Attempted to %s while already registered", __FUNCTION__); 121 | return 0; //Technically it succeeded 122 | } 123 | 124 | int out; 125 | //This has to be done once (we're assuming cmdline doesn't change without reboot). In case this submodule is 126 | // re-registered the filtrated_cmdline is left as-is and reused 127 | if (!filtrated_cmdline && (out = filtrate_cmdline(cmdline_blacklist)) != 0) 128 | return out; 129 | 130 | ov_cmdline_proc_show = override_symbol("cmdline_proc_show", cmdline_proc_show_filtered); 131 | if (unlikely(IS_ERR(ov_cmdline_proc_show))) { 132 | out = PTR_ERR(ov_cmdline_proc_show); 133 | pr_loc_err("Failed to override cmdline_proc_show - error %d", out); 134 | ov_cmdline_proc_show = NULL; 135 | return out; 136 | } 137 | 138 | pr_loc_inf("/proc/cmdline sanitized"); 139 | 140 | return 0; 141 | } 142 | 143 | int unregister_stealth_sanitize_cmdline(void) 144 | { 145 | if (unlikely(!ov_cmdline_proc_show)) { 146 | pr_loc_bug("Attempted to %s while it's not registered", __FUNCTION__); 147 | return 0; //Technically it succeeded 148 | } 149 | 150 | int out = restore_symbol(ov_cmdline_proc_show); 151 | //We deliberately fall through here without checking as we have to free stuff at this point no matter what 152 | 153 | kfree(filtrated_cmdline); 154 | filtrated_cmdline = NULL; 155 | 156 | if (likely(out == 0)) 157 | pr_loc_inf("Original /proc/cmdline restored"); 158 | else 159 | pr_loc_err("Failed to restore original /proc/cmdline: org_cmdline_proc_show failed - error %d", out); 160 | 161 | return out; 162 | } -------------------------------------------------------------------------------- /internal/stealth/sanitize_cmdline.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_SANITIZE_CMDLINE_H 2 | #define REDPILL_SANITIZE_CMDLINE_H 3 | 4 | #include "../../config/cmdline_delegate.h" //MAX_BLACKLISTED_CMDLINE_TOKENS 5 | 6 | /** 7 | * Register submodule sanitizing /proc/cmdline 8 | * 9 | * After registration /proc/cmdline will be non-destructively cleared from entries listed in cmdline_blacklist param. 10 | * It can be reversed using unregister_stealth_sanitize_cmdline() 11 | * 12 | * @return 0 on success, -E on error 13 | */ 14 | int register_stealth_sanitize_cmdline(cmdline_token *cmdline_blacklist[MAX_BLACKLISTED_CMDLINE_TOKENS]); 15 | 16 | /** 17 | * Reverses what register_stealth_sanitize_cmdline() did 18 | * 19 | * @return 0 on success, -E on error 20 | */ 21 | int unregister_stealth_sanitize_cmdline(void); 22 | 23 | #endif //REDPILL_SANITIZE_CMDLINE_H 24 | -------------------------------------------------------------------------------- /internal/uart/uart_swapper.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_UART_SWAPPER_H 2 | #define REDPILL_UART_SWAPPER_H 3 | 4 | /** 5 | * Swaps two given UARTs/serial prots so that their data paths are exchanged without the change of /dev/tty# 6 | * 7 | * This method is blind to whether UARTs were swapped during kernel build. However, it's the reason it exists to un-swap 8 | * these stupid ports. You can swap any ports you want. It's not recommended to swap ports which are in different run 9 | * state (i.e. one is active/open/running and the other one is not). In such cases the swap will be attempted BUT the 10 | * port which was active may not be usable until re-opened (usually it will be, but there's a chance). 11 | * 12 | * @param from Line number (line = ttyS#, so line=0 = ttyS0; this is universal across Linux UART subsystem)) 13 | * @param to Line number 14 | * @return 0 on success or -E on error 15 | */ 16 | int uart_swap_hw_output(unsigned int from, unsigned char to); 17 | 18 | #endif //REDPILL_UART_SWAPPER_H 19 | -------------------------------------------------------------------------------- /internal/uart/virtual_uart.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_VIRTUAL_UART_H 2 | #define REDPILL_VIRTUAL_UART_H 3 | 4 | #include //bool 5 | 6 | /** 7 | * Length of the RX/TX FIFO in bytes 8 | * Do NOT change this value just because you want to inject more data at once - it's a hardware-defined property 9 | */ 10 | #define VUART_FIFO_LEN 16 11 | 12 | /** 13 | * Defines maximum threshold possible; in practice this means you will never get any THRESHOLD events but only ID:E and 14 | * FULL ones. 15 | */ 16 | #define VUART_THRESHOLD_MAX INT_MAX 17 | 18 | /** 19 | * Specified the reason why the vUART flushed the buffer 20 | * 21 | * This value carries one guarantee: they will be evaluated in that order of priority. If you set a threshold to exactly 22 | * VUART_FIFO_LEN and the application sends exactly VUART_FIFO_LEN bytes you will get a reason of VUART_FLUSH_THRESHOLD 23 | * even thou all three conditions are met. If you set the threshold to 10 and the app sends 12 bytes you will get a call 24 | * with VUART_FLUSH_THRESHOLD after 10 bytes, then another one with two bytes and VUART_FLUSH_IDLE. 25 | */ 26 | typedef enum { 27 | //Threshold specified while setting the callback has been reached 28 | VUART_FLUSH_THRESHOLD, 29 | 30 | //Kernel put the transmitter in an idle mode, which most of the time indicated end of transmission/packet 31 | VUART_FLUSH_IDLE, 32 | 33 | //FIFO was full before threshold has been reached and the transmission isn't finished yet 34 | VUART_FLUSH_FULL, 35 | } vuart_flush_reason ; 36 | 37 | /** 38 | * Represents a callback signature 39 | * 40 | * @param line UART# where the data arrived; you can ignore it if you registered only one UART 41 | * @param buffer Place where you can read the data from; as of now this is the same buffer but don't rely on this! 42 | * @param len Number of bytes you're allowed to read from beginning of the buffer 43 | * @param reason Denotes why the vUART decided to flush the buffer to the callback 44 | */ 45 | typedef void (vuart_callback_t)(int line, const char *buffer, unsigned int len, vuart_flush_reason reason); 46 | 47 | /** 48 | * Adds a virtual UART device 49 | * 50 | * Calling this function will immediately yank the port from the real one and began capturing its output, so that no 51 | * data will leave through the real one. However, by itself the data will not be delivered anywhere until you call 52 | * vuart_set_tx_callback(), which you can do before or after calling vuart_add_device(). 53 | * 54 | * @param line UART number to replace, e.g. 0 for ttyS0. On systems with inverted UARTs you should use the real one, so 55 | * even if ttyS0 points to 2nd physical port this method will ALWAYS use the one corresponding to ttyS* 56 | * 57 | * @return 0 on success or -E on error 58 | */ 59 | int vuart_add_device(int line); 60 | 61 | /** 62 | * Removes a virtual UART device 63 | * 64 | * Calling this function restores previously replaced port. Unlike vuart_add_device() this function WILL alter TX 65 | * callbacks by removing all of them. The reasoning behind this is that adding a device and later on adding/changing 66 | * callbacks makes sense while removing the device and potentially leaving broken pointers can lead to nasty and hard to 67 | * trace bugs. 68 | * 69 | * @param line UART number to replace, e.g. 0 for ttyS0. On systems with inverted UARTs you should use the real one, so 70 | * even if ttyS0 points to 2nd physical port this method will ALWAYS use the one corresponding to ttyS* 71 | * 72 | * @return 0 on success or -E on error 73 | */ 74 | int vuart_remove_device(int line); 75 | 76 | /** 77 | * Injects data into RX stream of the port 78 | * 79 | * It may be confusing at first what's TX and RX in the context here. Imagine a physical chip connected to a computer 80 | * with some bus (not UART). The chip's RX is what the chip would get from *something*. So injecting data into RX of the 81 | * chip causes the data to be processed by the chip and arrive in the kernel and then in the application which opened 82 | * the port. So while TX implies "transmission" from the perspective of the chip and the app opening the port it's an 83 | * RX side. This naming is consistent with what the whole 8250 subsystem uses. 84 | * 85 | * @param line UART number to replace, e.g. 0 for ttyS0. On systems with inverted UARTs you should use the real one, so 86 | * even if ttyS0 points to 2nd physical port this method will ALWAYS use the one corresponding to ttyS* 87 | * @param buffer Pointer to a buffer where we will read from. There's no assumption as to what the buffer contains. 88 | * @param length Length to read from the buffer up to VUART_FIFO_LEN 89 | * 90 | * @return 0 on success or -E on error 91 | */ 92 | int vuart_inject_rx(int line, const char *buffer, int length); 93 | 94 | /** 95 | * Set a function which will be called upon data transmission by the port opener 96 | * 97 | * In short you will get data which some app (e.g. cat file > /dev/ttyS0) sent. If you're confused by the RX/TX read the 98 | * comment for inject_rx(). 99 | * 100 | * Example of the callback usage: 101 | * //The len is the real number of bytes available to read. The buffer ptrs is the same as you gave to set_tx_ 102 | * void dummy_tx_callback(int line, const char *buffer, int len, vuart_flush_Reason reason) { 103 | * pr_loc_inf("TX @ ttyS%d: |%.*s|", line, len, buffer); 104 | * } 105 | * //.... 106 | * char buf[VUART_FIFO_LEN]; //Your buffer should be able to accommodate at least VUART_FIFO_LEN 107 | * vuart_set_tx_callback(TRY_PORT, dummy_tx_callback, buf, VUART_FIFO_LEN); 108 | * 109 | * WARNING: 110 | * You callback should be multithreading-aware. It may be called from different contexts. You shouldn't do a lot of work 111 | * on the thread where your callback has been called. If you need something more copy the buffer and process it on a 112 | * separate thread. 113 | * 114 | * @param line UART number to replace, e.g. 0 for ttyS0. On systems with inverted UARTs you should use the real one, so 115 | * even if ttyS0 points to 2nd physical port this method will ALWAYS use the one corresponding to ttyS* 116 | * @param cb Function to be called; call it with a NULL ptr to remove callback, see docblock for vuart_callback_t 117 | * @param buffer A pointer to a buffer where data will be placed. The buffer should be able to accommodate 118 | * VUART_FIFO_LEN number of bytes. The buffer you pass will be the same one as passed back during a call 119 | * @param threshold a *HINT* how many bytes at minimum should be deposited in the FIFO before callback is called. Keep 120 | * in mind that this is just a hint and you callback may be called sooner (e.g. when a client program 121 | * wrote only a single byte using e.g. echo -n X > /dev/ttyS0). 122 | * @return 0 on success or -E on error 123 | */ 124 | int vuart_set_tx_callback(int line, vuart_callback_t *cb, char *buffer, int threshold); 125 | 126 | #endif //REDPILL_VIRTUAL_UART_H 127 | -------------------------------------------------------------------------------- /internal/uart/vuart_internal.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_VUART_INTERNAL_H 2 | #define REDPILL_VUART_INTERNAL_H 3 | 4 | #include 5 | #ifndef VUART_USE_TIMER_FALLBACK 6 | #include 7 | #endif 8 | 9 | 10 | //Lock/unlock vdev for registries operations 11 | #define lock_vuart(vdev) spin_lock_irqsave((vdev)->lock, (vdev)->lock_flags); 12 | #define unlock_vuart(vdev) spin_unlock_irqrestore((vdev)->lock, (vdev)->lock_flags); 13 | 14 | //In some circumstances operations may be performed on the chip before or after the chip is initialized. If it is 15 | // initialized we need a lock first; otherwise we do not. This is a shortcut for this opportunistic/conditional locking. 16 | #define lock_vuart_oppr(vdev) if ((vdev)->initialized) { lock_vuart(vdev); } 17 | #define unlock_vuart_oppr(vdev) if ((vdev)->initialized) { unlock_vuart(vdev); } 18 | 19 | #define validate_isa_line(line) \ 20 | if (unlikely((line) > SERIAL8250_LAST_ISA_LINE)) { \ 21 | pr_loc_bug("%s failed - requested line %d but kernel supports only %d", __FUNCTION__, line, \ 22 | SERIAL8250_LAST_ISA_LINE); \ 23 | return -EINVAL; \ 24 | } 25 | 26 | /** 27 | * An emulated 16550A chips internal state 28 | * 29 | * See http://caro.su/msx/ocm_de1/16550.pdf for details; registers are on page 9 (Table 2) 30 | */ 31 | struct serial8250_16550A_vdev { 32 | //Port properties 33 | u8 line; 34 | u16 iobase; 35 | u8 irq; 36 | unsigned int baud; 37 | 38 | //The 8250 driver port structure - it will be populated as soon as 8250 gives us the real pointer 39 | struct uart_port *up; 40 | 41 | //Chip emulated FIFOs 42 | struct kfifo *tx_fifo; //character to be sent (aka what we've got from the OS) 43 | struct kfifo *rx_fifo; //characters received (aka what we want the OS to get from us) 44 | 45 | //Chip registries (they're considered volatile but there's a spinlock protecting them) 46 | u8 rhr; //Receiver Holding Register (characters received) 47 | u8 thr; //Transmitter Holding Register (characters REQUESTED to be sent, TSR will contain these to be TRANSMITTED) 48 | u8 ier; //Interrupt Enable Register 49 | u8 iir; //Interrupt ID Register (same as ISR/Interrupt Status Register) 50 | u8 fcr; //FIFO Control Register (not really used but holds values written to it) 51 | u8 lcr; //Line Control Register (not really used but holds values written to it) 52 | u8 mcr; //Modem Control Register (used to control autoflow) 53 | u8 lsr; //Line Status Register 54 | u8 msr; //Modem Status Register 55 | u8 scr; //SCratch pad Register (in the original docs refered to as SPR, but linux uses SCR name) 56 | u8 dll; //Divisor Lat Least significant byte (not really used but holds values written to it) 57 | u8 dlm; //Divisor Lat Most significant byte (not really used but holds values written to it; also called DLH) 58 | u8 psd; //Prescaler Division (not really used but holds values written to it) 59 | 60 | //Some operations (e.g. FIFO access) must be locked 61 | bool initialized:1; 62 | bool registered:1; //whether the vdev is actually registered with 8250 subsystem 63 | spinlock_t *lock; 64 | unsigned long lock_flags; 65 | 66 | #ifndef VUART_USE_TIMER_FALLBACK 67 | //We emulate (i.e. self-trigger) interrupts on threads 68 | struct task_struct *virq_thread; //where fake interrupt code is executed 69 | wait_queue_head_t *virq_queue; //wait queue used to put thread to sleep 70 | #endif 71 | }; 72 | 73 | #endif //REDPILL_VUART_INTERNAL_H 74 | -------------------------------------------------------------------------------- /internal/uart/vuart_virtual_irq.c: -------------------------------------------------------------------------------- 1 | #ifndef VUART_USE_TIMER_FALLBACK 2 | 3 | #include "vuart_virtual_irq.h" 4 | #include "vuart_internal.h" 5 | #include "../../common.h" 6 | #include "../../debug/debug_vuart.h" 7 | #include //UART_* consts 8 | #include //running vIRQ thread 9 | #include //wait queue handling (init_waitqueue_head etc.) 10 | #include //serial8250_handle_irq 11 | 12 | //Default name of the thread for vIRQ 13 | #ifndef VUART_THREAD_FMT 14 | #define VUART_THREAD_FMT "vuart/%d-ttyS%d" 15 | #endif 16 | 17 | /** 18 | * Function running on a separate kernel thread responsible for simulating the IRQ call (normally done via hardware 19 | * interrupt triggering CPU to invoke Linux IRQ subsystem) 20 | * 21 | * There's no sane way to trigger IRQs in the low range used by 8250 UARTs. A pure asm call of "int $4" will result in a 22 | * crash (yes, we did try first ;)). So instead of hacking around the kernel we simply used the 8250 public interface to 23 | * trigger interrupt routines and implemented a small IRQ handling subsystem on our own. 24 | * @param data 25 | * @return 26 | */ 27 | static int virq_thread(void *data) 28 | { 29 | allow_signal(SIGKILL); 30 | 31 | int out = 0; 32 | struct serial8250_16550A_vdev *vdev = data; 33 | 34 | uart_prdbg("%s started for ttyS%d pid=%d", __FUNCTION__, vdev->line, current->pid); 35 | while(likely(!kthread_should_stop())) { 36 | wait_event_interruptible(*vdev->virq_queue, !(vdev->iir & UART_IIR_NO_INT) || unlikely(kthread_should_stop())); 37 | if (unlikely(signal_pending(current))) { 38 | uart_prdbg("%s started for ttyS%d pid=%d received signal", __FUNCTION__, vdev->line, current->pid); 39 | out = -EPIPE; 40 | break; 41 | } 42 | 43 | if (unlikely(kthread_should_stop())) 44 | break; 45 | 46 | if (unlikely(!vdev->up)) { 47 | pr_loc_bug("Cannot call serial8250 interrupt handler - port not captured (yet?)"); 48 | continue; 49 | } 50 | 51 | uart_prdbg("Calling serial8250 interrupt handler"); 52 | serial8250_handle_irq(vdev->up, vdev->iir); 53 | } 54 | uart_prdbg("%s stopped for ttyS%d pid=%d exit=%d", __FUNCTION__, vdev->line, current->pid, out); 55 | 56 | //that can lead to a small memory leak for virq_queue if thread is killed outisde disable_interrupts() but this 57 | // shouldn't normally happen unless something goes horribly wrong 58 | vdev->virq_thread = NULL; 59 | 60 | return out; 61 | } 62 | 63 | int vuart_enable_interrupts(struct serial8250_16550A_vdev *vdev) 64 | { 65 | int out; 66 | pr_loc_dbg("Enabling vIRQ for ttyS%d", vdev->line); 67 | lock_vuart(vdev); 68 | 69 | if (unlikely(!vdev->initialized)) { 70 | pr_loc_bug("ttyS%d is not initialized as vUART", vdev->line); 71 | out = -ENODEV; 72 | goto error_unlock_free; 73 | } 74 | 75 | if (unlikely(vuart_virq_active(vdev))) { 76 | pr_loc_bug("Interrupts are already enabled & scheduled for ttyS%d", vdev->line); 77 | out = -EBUSY; 78 | goto error_unlock_free; 79 | } 80 | 81 | if (!(vdev->virq_queue = kmalloc(sizeof(wait_queue_head_t), GFP_KERNEL)) || 82 | !(vdev->virq_thread = kmalloc(sizeof(struct task_struct), GFP_KERNEL))) { 83 | out = -ENOMEM; 84 | pr_loc_crt("kernel memory alloc failure - tried to reserve memory for vIRQ structures"); 85 | goto error_unlock_free; 86 | } 87 | 88 | init_waitqueue_head(vdev->virq_queue); 89 | unlock_vuart(vdev); //we can safely unlock after reserving memory but before starting thread (so we're not atomic) 90 | 91 | #pragma GCC diagnostic push 92 | #pragma GCC diagnostic ignored "-Wformat-extra-args" 93 | //VUART_THREAD_FMT can resolve to anonymized version without line or even IRQ# 94 | vdev->virq_thread = kthread_run(virq_thread, vdev, VUART_THREAD_FMT, vdev->irq, vdev->line); 95 | #pragma GCC diagnostic pop 96 | if (IS_ERR(vdev->virq_thread)) { 97 | out = PTR_ERR(vdev->virq_thread); 98 | pr_loc_bug("Failed to start vIRQ thread"); 99 | goto error_free; 100 | } 101 | pr_loc_dbg("vIRQ fully enabled for for ttyS%d", vdev->line); 102 | 103 | return 0; 104 | 105 | error_unlock_free: 106 | unlock_vuart(vdev); 107 | error_free: 108 | if (vdev->virq_queue) { 109 | kfree(vdev->virq_queue); 110 | vdev->virq_queue = NULL; 111 | } 112 | if (vdev->virq_thread) { 113 | kfree(vdev->virq_thread); 114 | vdev->virq_thread = NULL; 115 | } 116 | return out; 117 | } 118 | 119 | int vuart_disable_interrupts(struct serial8250_16550A_vdev *vdev) 120 | { 121 | int out; 122 | pr_loc_dbg("Disabling vIRQ for ttyS%d", vdev->line); 123 | lock_vuart(vdev); 124 | 125 | if (unlikely(!vdev->initialized)) { 126 | pr_loc_bug("ttyS%d is not initialized as vUART", vdev->line); 127 | out = -ENODEV; 128 | goto out_unlock; 129 | } 130 | 131 | if (unlikely(!vuart_virq_active(vdev))) { 132 | pr_loc_bug("Interrupts are not enabled/scheduled for ttyS%d", vdev->line); 133 | out = -EBUSY; 134 | goto out_unlock; 135 | } 136 | 137 | out = kthread_stop(vdev->virq_thread); 138 | if (out < 0) { 139 | pr_loc_bug("Failed to stop vIRQ thread"); 140 | goto out_unlock; 141 | } 142 | 143 | kfree(vdev->virq_thread); 144 | vdev->virq_thread = NULL; 145 | pr_loc_dbg("vIRQ disabled for ttyS%d", vdev->line); 146 | 147 | out_unlock: 148 | unlock_vuart(vdev); 149 | 150 | return 0; 151 | } 152 | #endif -------------------------------------------------------------------------------- /internal/uart/vuart_virtual_irq.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_VUART_VIRTUAL_IRQ_H 2 | #define REDPILL_VUART_VIRTUAL_IRQ_H 3 | 4 | #ifdef VUART_USE_TIMER_FALLBACK 5 | #define vuart_virq_supported() 0 6 | #define vuart_virq_wake_up(dummy) //noop 7 | #define vuart_enable_interrupts(dummy) (0) 8 | #define vuart_disable_interrupts(dummy) (0) 9 | 10 | #else //VUART_USE_TIMER_FALLBACK 11 | #include "vuart_internal.h" 12 | 13 | #define vuart_virq_supported() 1 14 | #define vuart_virq_active(vdev) (!!(vdev)->virq_thread) 15 | #define vuart_virq_wake_up(vdev) if (vuart_virq_active(vdev)) { wake_up_interruptible(vdev->virq_queue); } 16 | int vuart_enable_interrupts(struct serial8250_16550A_vdev *vdev); 17 | int vuart_disable_interrupts(struct serial8250_16550A_vdev *vdev); 18 | #endif //VUART_USE_TIMER_FALLBACK 19 | 20 | #endif //REDPILL_VUART_VIRTUAL_IRQ_H -------------------------------------------------------------------------------- /internal/virtual_pci.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_VIRTUAL_PCI_H 2 | #define REDPILL_VIRTUAL_PCI_H 3 | 4 | #include 5 | 6 | /* 7 | * The following macros are useful for converting PCI_CLASS_* constants to individual values in structs 8 | * 31 .................. 0 9 | * [class][sub][prog][rev] (each is 8 bit number, often times class-sub-prog is represented as on 24-bit int) 10 | * 11 | * For more information see header comment in the corresponding .c file. 12 | */ 13 | #define U24_CLASS_TO_U8_CLASS(x) (((x) >> 16) & 0xFF) 14 | #define U24_CLASS_TO_U8_SUBCLASS(x) (((x) >> 8) & 0xFF) 15 | #define U24_CLASS_TO_U8_PROGIF(x) ((x) & 0xFF) 16 | #define U16_CLASS_TO_U8_CLASS(x) (((x) >> 8) & 0xFF) 17 | #define U16_CLASS_TO_U8_SUBCLASS(x) ((x) & 0xFF) 18 | 19 | //Some helpful constants on top of what's in linux/pci_ids.h & linux/pci_regs.h 20 | #define PCI_DSC_NO_INT_LINE 0xFF 21 | #define PCI_DSC_NO_INT_PIN 0x00 22 | #define PCI_DSC_PROGIF_NONE 0x00 23 | #define PCI_DSC_REV_NONE 0x00 24 | #define PCI_DSC_NULL_BAR 0x00000000 25 | #define PCI_DSC_NULL_CAP 0x00 26 | #define PCI_DSC_RSV8 0x00 27 | #define PCI_DSC_RSV16 0x0000 28 | #define PCI_DSC_INF_LATENCY 0xFF //i.e. accepts any latency in access 29 | #define PCI_DSC_ZERO_BURST 0xFF //i.e. doesn't need any length of burst 30 | #define PCI_DSC_BIST_NONE 0x00 31 | 32 | //See https://en.wikipedia.org/wiki/PCI_configuration_space#/media/File:Pci-config-space.svg 33 | //This struct MUST be packed to allow for easy reading, see https://kernelnewbies.org/DataAlignment 34 | struct pci_dev_descriptor { 35 | u16 vid; //Vendor ID 36 | u16 dev; //Device ID 37 | 38 | u16 command; //see PCI_COMMAND_*. Simply do "dev.command |= PCI_COMMAND_xxx" to set a flag. 39 | u16 status; //see PCI_STATUS_*. Simply do "dev.status |= PCI_STATUS_xxx" to set a flag. 40 | 41 | u8 rev_id; 42 | u8 prog_if; // ] 43 | u8 subclass; // ]-> prof_if, subclass, and class are normally represented as 24-bit class code 44 | u8 class; // ] 45 | 46 | u8 cache_line_size; 47 | u8 latency_timer; 48 | u8 header_type; //see PCI_HEADER_TYPE_* 49 | u8 bist; //see PCI_BIST_* 50 | 51 | u32 bar0; 52 | u32 bar1; 53 | u32 bar2; 54 | u32 bar3; 55 | u32 bar4; 56 | u32 bar5; 57 | 58 | u32 cardbus_cis; 59 | 60 | u16 subsys_vid; 61 | u16 subsys_id; 62 | 63 | u32 exp_rom_base_addr; //see PCI_ROM_* (esp PCI_ROM_ADDRESS_MASK) 64 | 65 | u8 cap_ptr; 66 | u8 reserved_34_8_15; //should be 0x00 67 | u16 reserved_34_16_31; //should be 0x00 68 | 69 | u32 reserved_38h; 70 | 71 | u8 interrupt_line; 72 | u8 interrupt_pin; 73 | u8 min_gnt; 74 | u8 max_lat; 75 | } __packed; 76 | extern const struct pci_dev_descriptor pci_dev_conf_default_normal_dev; //See details in the .c file 77 | 78 | //Support for bridges wasn't tested 79 | struct pci_pci_bridge_descriptor { 80 | u16 vid; //Vendor ID 81 | u16 dev; //Device ID 82 | 83 | u16 command; //see PCI_COMMAND_*. Simply do "dev.command |= PCI_COMMAND_xxx" to set a flag. 84 | u16 status; //see PCI_STATUS_*. Simply do "dev.status |= PCI_STATUS_xxx" to set a flag. 85 | 86 | u8 rev_id; 87 | u8 prog_if; // ] 88 | u8 subclass; // ]-> prof_if, subclass, and class are normally represented as 24-bit class code 89 | u8 class; // ] 90 | 91 | u8 cache_line_size; 92 | u8 latency_timer; 93 | u8 header_type; //see PCI_HEADER_TYPE_* 94 | u8 bist; //see PCI_BIST_* 95 | 96 | u32 bar0; 97 | u32 bar1; 98 | 99 | u8 pri_bus_no; 100 | u8 sec_bus_no; 101 | u8 subord_bus_no; 102 | u8 sec_lat_timer; 103 | 104 | u8 io_base; 105 | u8 io_limit; 106 | u16 sec_status; 107 | 108 | u16 mem_base; 109 | u16 mem_limit; 110 | 111 | u16 prefetch_mem_base; 112 | u16 prefetch_mem_limit; 113 | 114 | u32 prefetch_base_up32b; 115 | u32 prefetch_limit_up32b; 116 | 117 | u16 io_base_up16b; 118 | u16 io_limit_up16b; 119 | 120 | u8 cap_ptr; 121 | u8 reserved_34_8_15; //should be 0x00 122 | u16 reserved_34_16_31; //should be 0x00 123 | 124 | u32 exp_rom_base_addr; //see PCI_ROM_* (esp PCI_ROM_ADDRESS_MASK) 125 | 126 | u8 interrupt_line; 127 | u8 interrupt_pin; 128 | u16 bridge_ctrl; 129 | } __packed; 130 | 131 | //This is currently not implemented 132 | struct pci_dev_capability { 133 | u8 cap_id; //see PCI_CAP_ID_*, set to 0x00 to denote null-capability 134 | u8 cap_next; //offset where next capability exists, set to 0x00 to denote null-capability 135 | u8 cap_data[]; 136 | } __packed; 137 | 138 | /** 139 | * Adds a single new device (along with the bus if needed) 140 | * 141 | * If you don't want to create the descriptor from scratch you can use "const struct pci_dev_conf_default_normal_dev" 142 | * while setting some missing params (see .c file header for details). 143 | * Note: you CAN reuse the same descriptor under multiple BDFs (bus_no/dev_no/fn_no) 144 | * 145 | * @param bus_no (0x00 - 0xFF) 146 | * @param dev_no (0x00 - 0x20) 147 | * @param descriptor Pointer to pci_dev_descriptor or pci_pci_bridge_descriptor 148 | * @return virtual_device ptr or error pointer (ERR_PTR(-E)) 149 | */ 150 | const struct virtual_device * 151 | vpci_add_single_device(unsigned char bus_no, unsigned char dev_no, struct pci_dev_descriptor *descriptor); 152 | 153 | /** 154 | * See vpci_add_single_device() for details 155 | */ 156 | const struct virtual_device * 157 | vpci_add_single_bridge(unsigned char bus_no, unsigned char dev_no, struct pci_pci_bridge_descriptor *descriptor); 158 | 159 | 160 | /* 161 | * Adds a new multifunction device (along with the bus if needed) 162 | * 163 | * Warning about multifunctional devices 164 | * - this function has a slight limitation due to how Linux scans devices. You HAVE TO add fn_no=0 entry as the LAST 165 | * one when calling it multiple times. Kernel scans devices only once for changes and if it finds fn=0 and it's the 166 | * only one (i.e. you added fn=0 first) adding more functions will not populate them (as kernel will never re-scan 167 | * the device). 168 | * - As per PCI spec Linux doesn't allow devices to have fn>0 if they don't have corresponding fn=0 entry 169 | * 170 | * @param bus_no (0x00 - 0xFF) 171 | * @param dev_no (0x00 - 0x20) 172 | * @param fn_no (0x00 - 0x07) 173 | * @param descriptor Pointer to pci_dev_descriptor or pci_pci_bridge_descriptor 174 | * @return virtual_device ptr or error pointer (ERR_PTR(-E)) 175 | */ 176 | const struct virtual_device * 177 | vpci_add_multifunction_device(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, 178 | struct pci_dev_descriptor *descriptor); 179 | 180 | /** 181 | * See vpci_add_multifunction_device() for details 182 | */ 183 | const struct virtual_device * 184 | vpci_add_multifunction_bridge(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, 185 | struct pci_pci_bridge_descriptor *descriptor); 186 | 187 | /** 188 | * Removes all previously added devices and buses 189 | * 190 | * Known bug: while you can remove things and they will be gone from the system you CANNOT re-add the same under the 191 | * same BFD coordinates. This will cause the kernel to complain about duplicated internal sysfs entries. It's most 192 | * likely an old kernel bug (we tried everything... it doesn't work). 193 | * 194 | * @param bus_no 195 | * @param dev_no 196 | * @param fn_no 197 | * @param descriptor 198 | * @return 199 | */ 200 | int vpci_remove_all_devices_and_buses(void); 201 | 202 | #endif //REDPILL_VIRTUAL_PCI_H 203 | -------------------------------------------------------------------------------- /redpill_main.c: -------------------------------------------------------------------------------- 1 | #include "internal/stealth.h" 2 | #include "redpill_main.h" 3 | #include "config/runtime_config.h" 4 | #include "common.h" //commonly used headers in this module 5 | #include "internal/intercept_execve.h" //Handling of execve() replacement 6 | #include "internal/scsi/scsi_notifier.h" //the missing pub/sub handler for SCSI driver 7 | #include "internal/ioscheduler_fixer.h" //reset_elevator() to correct elevator= boot cmdline 8 | #include "config/cmdline_delegate.h" //Parsing of kernel cmdline 9 | #include "shim/boot_device_shim.h" //Registering & deciding between boot device shims 10 | #include "shim/bios_shim.h" //Shimming various mfgBIOS functions to make them happy 11 | #include "shim/block_fw_update_shim.h" //Prevent firmware update from running 12 | #include "shim/disable_exectutables.h" //Disable common problematic executables 13 | #include "shim/pci_shim.h" //Handles PCI devices emulation 14 | #include "shim/storage/smart_shim.h" //Handles emulation of SMART data for devices without it 15 | #include "shim/storage/sata_port_shim.h" //Handles VirtIO & SAS storage devices/disks peculiarities 16 | #include "shim/uart_fixer.h" //Various fixes for UART weirdness 17 | #include "shim/pmu_shim.h" //Emulates the platform management unit 18 | 19 | //Handle versioning stuff 20 | #ifndef RP_VERSION_POSTFIX 21 | #define RP_VERSION_POSTFIX "(NULL)" 22 | #endif 23 | #define RP_VERSION_MAJOR 0 24 | #define RP_VERSION_MINOR 5 25 | #define STRINGIFY(x) #x 26 | #define VERSIONIFY(major,minor,postfix) "v" STRINGIFY(major) "." STRINGIFY(minor) "-" postfix 27 | #define RP_VERSION_STR VERSIONIFY(RP_VERSION_MAJOR, RP_VERSION_MINOR, RP_VERSION_POSTFIX) 28 | 29 | /** 30 | * Force panic to land on a stack trace 31 | * 32 | * This ensures we always get this on the stack trace so that we know it was an intentional crash due to a detected 33 | * error rather than an accidental bug. 34 | */ 35 | void noinline __noreturn rp_crash(void) { 36 | //Deliberately not reveling any context in case we're running in stealth mode 37 | //This message is a generic one from arch/x86/kernel/dumpstack.c 38 | panic("Fatal exception"); 39 | } 40 | 41 | static int __init init_(void) 42 | { 43 | int out = 0; 44 | 45 | pr_loc_dbg("================================================================================================"); 46 | pr_loc_inf("RedPill %s loading...", RP_VERSION_STR); 47 | 48 | if ( 49 | (out = extract_config_from_cmdline(¤t_config)) != 0 //This MUST be the first entry 50 | || (out = populate_runtime_config(¤t_config)) != 0 //This MUST be second 51 | || (out = register_uart_fixer(current_config.hw_config)) != 0 //Fix consoles ASAP 52 | || (out = register_scsi_notifier()) != 0 //Load SCSI notifier so that boot shim (& others) can use it 53 | || (out = register_sata_port_shim()) //This should be bfr boot shim as it can fix some things need by boot 54 | || (out = register_boot_shim(¤t_config.boot_media)) //Make sure we're quick with this one 55 | || (out = register_execve_interceptor()) != 0 //Register this reasonably high as other modules can use it blindly 56 | || (out = register_bios_shim(current_config.hw_config)) != 0 57 | || (out = register_disable_executables_shim()) != 0 58 | || (out = register_fw_update_shim()) != 0 59 | #ifndef DBG_DISABLE_UNLOADABLE 60 | || (out = register_pci_shim(current_config.hw_config)) != 0 //it's a core hw but it's not checked early 61 | #endif 62 | || (out = register_disk_smart_shim()) != 0 //provide fake SMART to userspace 63 | || (out = register_pmu_shim(current_config.hw_config)) != 0 //this is used as early as mfgBIOS loads (=late) 64 | || (out = initialize_stealth(¤t_config)) != 0 //Should be after any shims to let shims have real stuff 65 | || (out = reset_elevator()) != 0 //Cosmetic, can be the last one 66 | ) 67 | goto error_out; 68 | 69 | pr_loc_inf("RedPill %s loaded successfully (stealth=%d)", RP_VERSION_STR, STEALTH_MODE); 70 | return 0; 71 | 72 | error_out: 73 | pr_loc_crt("RedPill %s cannot be loaded, initializer error=%d", RP_VERSION_STR, out); 74 | #ifdef KP_ON_LOAD_ERROR 75 | rp_crash(); 76 | #else 77 | return out; 78 | #endif 79 | } 80 | module_init(init_); 81 | 82 | #if STEALTH_MODE < STEALTH_MODE_FULL //module cannot be unloaded in full-stealth anyway 83 | static void __exit cleanup_(void) 84 | { 85 | pr_loc_inf("RedPill %s unloading...", RP_VERSION_STR); 86 | 87 | int (*cleanup_handlers[])(void ) = { 88 | uninitialize_stealth, 89 | unregister_pmu_shim, 90 | unregister_disk_smart_shim, 91 | #ifndef DBG_DISABLE_UNLOADABLE 92 | unregister_pci_shim, 93 | #endif 94 | unregister_fw_update_shim, 95 | unregister_disable_executables_shim, 96 | unregister_bios_shim, 97 | unregister_execve_interceptor, 98 | unregister_boot_shim, 99 | unregister_sata_port_shim, 100 | unregister_scsi_notifier, 101 | unregister_uart_fixer 102 | }; 103 | 104 | int out; 105 | for (int i = 0; i < ARRAY_SIZE(cleanup_handlers); i++) { 106 | pr_loc_dbg("Calling cleanup handler %pF<%p>", cleanup_handlers[i], cleanup_handlers[i]); 107 | out = cleanup_handlers[i](); 108 | if (out != 0) 109 | pr_loc_wrn("Cleanup handler %pF failed with code=%d", cleanup_handlers[i], out); 110 | } 111 | 112 | free_runtime_config(¤t_config); //A special snowflake ;) 113 | 114 | pr_loc_inf("RedPill %s is dead", RP_VERSION_STR); 115 | pr_loc_dbg("================================================================================================"); 116 | } 117 | module_exit(cleanup_); 118 | 119 | MODULE_AUTHOR("TTG"); 120 | MODULE_VERSION(RP_VERSION_STR); 121 | #endif 122 | 123 | 124 | MODULE_LICENSE("GPL"); 125 | -------------------------------------------------------------------------------- /redpill_main.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILLLKM_REDPILL_MAIN_H 2 | #define REDPILLLKM_REDPILL_MAIN_H 3 | 4 | #endif //REDPILLLKM_REDPILL_MAIN_H -------------------------------------------------------------------------------- /shim/bios/bios_hwcap_shim.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Overrides GetHwCapability to provide additional capabilities for older platforms (e.g. 3615xs) 3 | */ 4 | #include "bios_hwcap_shim.h" 5 | #include "../../common.h" 6 | #include "../shim_base.h" 7 | #include "../../internal/override/override_symbol.h" //overriding GetHWCapability 8 | #include "../../config/platform_types.h" //hw_config, platform_has_hwmon_* 9 | #include //CAPABILITY_*, CAPABILITY 10 | 11 | #define SHIM_NAME "mfgBIOS HW Capability" 12 | 13 | static const struct hw_config *hw_config = NULL; 14 | static override_symbol_inst *GetHwCapability_ovs = NULL; 15 | 16 | static void dbg_compare_cap_value(SYNO_HW_CAPABILITY id, int computed_support) 17 | { 18 | #ifdef DBG_HWCAP 19 | int org_fout = -1; 20 | CAPABILITY org_cap = { '\0' }; 21 | org_cap.id = id; 22 | int ovs_fout = call_overridden_symbol(org_fout, GetHwCapability_ovs, &org_cap); 23 | 24 | pr_loc_dbg("comparing GetHwCapability(id=%d)->support => computed=%d vs. real=%d [org_fout=%d, ovs_fout=%d]", id, 25 | computed_support, org_cap.support, org_fout, ovs_fout); 26 | #endif 27 | } 28 | 29 | static int GetHwCapability_shim(CAPABILITY *cap) 30 | { 31 | if (unlikely(!cap)) { 32 | pr_loc_err("Got NULL-ptr to %s", __FUNCTION__); 33 | return -EINVAL; 34 | } 35 | 36 | switch (cap->id) { 37 | case CAPABILITY_THERMAL: 38 | cap->support = platform_has_hwmon_thermal(hw_config) ? 1 : 0; 39 | dbg_compare_cap_value(cap->id, cap->support); 40 | return 0; 41 | 42 | case CAPABILITY_CPU_TEMP: 43 | cap->support = hw_config->has_cpu_temp; 44 | dbg_compare_cap_value(cap->id, cap->support); 45 | return 0; 46 | 47 | case CAPABILITY_FAN_RPM_RPT: 48 | cap->support = platform_has_hwmon_fan_rpm(hw_config) ? 1 : 0; 49 | dbg_compare_cap_value(cap->id, cap->support); 50 | return 0; 51 | 52 | case CAPABILITY_DISK_LED_CTRL: 53 | case CAPABILITY_AUTO_POWERON: 54 | case CAPABILITY_S_LED_BREATH: 55 | case CAPABILITY_MICROP_PWM: 56 | case CAPABILITY_CARDREADER: 57 | case CAPABILITY_LCM: { 58 | if (unlikely(!GetHwCapability_ovs)) { 59 | pr_loc_bug("%s() was called with proxy need when no OVS was available", __FUNCTION__); 60 | return -EIO; 61 | } 62 | 63 | int org_fout = -1; 64 | int ovs_fout = call_overridden_symbol(org_fout, GetHwCapability_ovs, cap); 65 | pr_loc_dbg("proxying GetHwCapability(id=%d)->support => real=%d [org_fout=%d, ovs_fout=%d]", cap->id, 66 | cap->support, org_fout, ovs_fout); 67 | 68 | return org_fout; 69 | } 70 | 71 | default: 72 | pr_loc_err("unknown GetHwCapability(id=%d) => out=-EINVAL", cap->id); 73 | return -EINVAL; 74 | } 75 | } 76 | 77 | int register_bios_hwcap_shim(const struct hw_config *hw) 78 | { 79 | shim_reg_in(); 80 | 81 | if (unlikely(GetHwCapability_ovs)) 82 | shim_reg_already(); 83 | 84 | hw_config = hw; 85 | override_symbol_or_exit_int(GetHwCapability_ovs, "GetHwCapability", GetHwCapability_shim); 86 | 87 | shim_reg_ok(); 88 | return 0; 89 | } 90 | 91 | int unregister_bios_hwcap_shim(void) 92 | { 93 | shim_ureg_in(); 94 | 95 | if (unlikely(!GetHwCapability_ovs)) 96 | return 0; //this is deliberately a noop 97 | 98 | int out = restore_symbol(GetHwCapability_ovs); 99 | if (unlikely(out != 0)) { 100 | pr_loc_err("Failed to restore GetHwCapability - error=%d", out); 101 | return out; 102 | } 103 | GetHwCapability_ovs = NULL; 104 | 105 | shim_ureg_ok(); 106 | return 0; 107 | } 108 | 109 | int reset_bios_hwcap_shim(void) 110 | { 111 | shim_reset_in(); 112 | put_overridden_symbol(GetHwCapability_ovs); 113 | GetHwCapability_ovs = NULL; 114 | 115 | shim_reset_ok(); 116 | return 0; 117 | } -------------------------------------------------------------------------------- /shim/bios/bios_hwcap_shim.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_BIOS_HWCAP_SHIM_H 2 | #define REDPILL_BIOS_HWCAP_SHIM_H 3 | 4 | #include //bool 5 | 6 | struct hw_config; 7 | int register_bios_hwcap_shim(const struct hw_config *hw); 8 | 9 | /** 10 | * This function should be called when we're unloading cleanly (=mfgBIOS is alive, we're going away). If the bios went 11 | * away on its own call reset_bios_hwcap_shim() 12 | */ 13 | int unregister_bios_hwcap_shim(void); 14 | 15 | /** 16 | * This function should be called when we're unloading because mfgBIOS went away. If the unload should be clean and 17 | * restore all mfgBIOS elements to its original state (i.e. the mfgBIOS is still loaded and not currently unloading) 18 | * call unregister_bios_hwcap_shim() instead. 19 | */ 20 | int reset_bios_hwcap_shim(void); 21 | 22 | 23 | #endif //REDPILL_BIOS_HWCAP_SHIM_H 24 | -------------------------------------------------------------------------------- /shim/bios/bios_hwmon_shim.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_BIOS_HWMON_SHIM_H 2 | #define REDPILL_BIOS_HWMON_SHIM_H 3 | 4 | #include 5 | 6 | struct hw_config; 7 | 8 | //This should be called from shim_bios_module() as it depends on the state of the vtable; it can be called many times 9 | int shim_bios_module_hwmon_entries(const struct hw_config *hw); 10 | int reset_bios_module_hwmon_shim(void); 11 | 12 | #endif //REDPILL_BIOS_HWMON_SHIM_H 13 | -------------------------------------------------------------------------------- /shim/bios/bios_shims_collection.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_BIOS_SHIMS_COLLECTION_H 2 | #define REDPILL_BIOS_SHIMS_COLLECTION_H 3 | 4 | #include //bool 5 | #include //struct module 6 | 7 | 8 | typedef struct hw_config hw_config_bios_shim_col; 9 | /** 10 | * Insert all the shims to the mfgBIOS 11 | */ 12 | bool shim_bios_module(const hw_config_bios_shim_col *hw, struct module *mod, unsigned long *vtable_start, unsigned long *vtable_end); 13 | 14 | /** 15 | * Removes all shims from the mfgBIOS & uninitializes all components used to shim bios module 16 | */ 17 | bool unshim_bios_module(unsigned long *vtable_start, unsigned long *vtable_end); 18 | 19 | /** 20 | * Forcefully forgets all original calls used to do unshim_bios() & cleans-up all other components 21 | * 22 | * This function is useful when the BIOS unloads without this module being unloaded - then there's no point in keeping 23 | * stale entries. This will also prevent warning regarding already-shimmed BIOS when it reloads. 24 | */ 25 | void reset_bios_shims(void); 26 | 27 | /** 28 | * Nullifies manual disks LED control 29 | * 30 | * The underlying reason for this isn't known but sometimes the manual LED control for disks (presumably used to blink 31 | * to identify disks from the UI) will cause a kernel panic pointing to the internals of mfgBIOS. The functionality is 32 | * implemented in the kernel (funcSYNOSATADiskLedCtrl) but it delegates the task to mfgBIOS via ioctl() 33 | * To prevent the crash we replace the manual LED altering subsystem in models which support it (because not all do). 34 | * 35 | * The kernel panic is most likely caused by the gap between early and full bios shimming where the bios will be early 36 | * shimmed, continue setting something and in between gets an ioctl to the LED api 37 | * 38 | * @return 0 on success or -E on error 39 | */ 40 | int shim_disk_leds_ctrl(const struct hw_config *hw); 41 | 42 | /** 43 | * Reverses what shim_disk_leds_ctrl did 44 | * 45 | * You CAN call this function any time, if shims weren't registered (yet) it will be a noop-call. 46 | * 47 | * @return 0 on success or -E on error 48 | */ 49 | int unshim_disk_leds_ctrl(void); 50 | 51 | /** 52 | * Used by mfgBIOS sub-shims. Should NOT be called from ANY other context as it depends on the internal state. 53 | */ 54 | void _shim_bios_module_entry(unsigned int idx, const void *new_sym_ptr); 55 | 56 | #endif //REDPILL_BIOS_SHIMS_COLLECTION_H 57 | -------------------------------------------------------------------------------- /shim/bios/rtc_proxy.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_RTC_PROXY_H 2 | #define REDPILL_RTC_PROXY_H 3 | 4 | #include "mfgbios_types.h" 5 | 6 | /** 7 | * Gets current RTC time (shims VTK_RTC_GET_TIME) 8 | */ 9 | int rtc_proxy_get_time(struct MfgCompatTime *mfgTime); 10 | 11 | /** 12 | * Sets current RTC time (shims VTK_RTC_SET_TIME) 13 | */ 14 | int rtc_proxy_set_time(struct MfgCompatTime *mfgTime); 15 | 16 | /** 17 | * Enables auto-power on functionality (shims VTK_RTC_INT_APWR). 18 | * 19 | * This is not REALLY implemented and only shimmed. Many motherboards don't handle it well or only support it from 20 | * certain ACPI PSTATEs. It is even more unsupported by hypervisors. If you REALLY need it create a bug report or a PR. 21 | */ 22 | int rtc_proxy_init_auto_power_on(void); 23 | 24 | /** 25 | * Gets time for auto-power on (shims VTK_RTC_GET_APWR). **See note for rtc_proxy_init_auto_power_on()** 26 | */ 27 | int rtc_proxy_get_auto_power_on(struct MfgCompatAutoPwrOn *mfgPwrOn); 28 | 29 | /** 30 | * Sets time for auto-power on (shims VTK_RTC_SET_APWR). **See note for rtc_proxy_init_auto_power_on()** 31 | */ 32 | int rtc_proxy_set_auto_power_on(struct MfgCompatAutoPwrOn *mfgPwrOn); 33 | 34 | /** 35 | * Disables auto-power on functionality (shims VTK_RTC_UINT_APWR). **See note for rtc_proxy_init_auto_power_on()** 36 | */ 37 | int rtc_proxy_uinit_auto_power_on(void); 38 | 39 | int unregister_rtc_proxy_shim(void); 40 | int register_rtc_proxy_shim(void); 41 | 42 | #endif //REDPILL_RTC_PROXY_H 43 | -------------------------------------------------------------------------------- /shim/bios_shim.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILLLKM_BIOS_SHIM_H 2 | #define REDPILLLKM_BIOS_SHIM_H 3 | 4 | struct hw_config; 5 | int register_bios_shim(const struct hw_config *hw); 6 | int unregister_bios_shim(void); 7 | 8 | #endif //REDPILLLKM_BIOS_SHIM_H 9 | -------------------------------------------------------------------------------- /shim/block_fw_update_shim.c: -------------------------------------------------------------------------------- 1 | /** 2 | * This rather simple shim prevents execution of a firmware update program when done in a one specific way 3 | * 4 | * During the OS installation process one of the steps executes a command "./H2OFFT-Lx64". This is a board firmware 5 | * update program. When executed under KVM it will crash the virtual CPU (and I wasn't brave enough to try it on bare 6 | * metal). All in all the execution must succeed from the perspective of the user-space and the file cannot be modified 7 | * due to checksum check. 8 | * 9 | * This shim hooks a execve() syscall and filter it through shim_sys_execve(). This in turn is self-explanatory for the 10 | * most part - it simply fakes successful execution without invoking anything. While such trickery can be detected (as 11 | * the real process is not really replaced) it is good enough for this case. 12 | * 13 | * Additionally, to make the firmware picking happy we need to pass a sanity check (which is presumably done to ensure 14 | * flasher doesn't accidentally brick an incorrect board) using DMI data. This is handled here by overriding one string 15 | * in the DMI data array (as the kernel API lacks any way of changing that). 16 | * 17 | * References: 18 | * - https://linux.die.net/man/3/execve 19 | * - https://0xax.gitbooks.io/linux-insides/content/SysCall/linux-syscall-4.html 20 | * - https://help.ubuntu.com/community/FimwareUpgrade/Insyde 21 | */ 22 | #define SHIM_NAME "firmware update blocker" 23 | 24 | #include "block_fw_update_shim.h" 25 | #include "shim_base.h" 26 | #include "../common.h" 27 | #include "../internal/intercept_execve.h" 28 | #include //dmi_get_system_info(), DMI_* 29 | 30 | #define DMI_MAX_LEN 512 31 | #define FW_BOARD_NAME "\x53\x79\x6e\x6f\x64\x65\x6e" 32 | #define FW_UPDATE_PATH "./H2OFFT-Lx64" 33 | 34 | static char dmi_product_name_backup[DMI_MAX_LEN] = { '\0' }; 35 | static void patch_dmi(void) 36 | { 37 | char *ptr = (char *)dmi_get_system_info(DMI_PRODUCT_NAME); 38 | size_t org_len = strlen(ptr); 39 | if (org_len > DMI_MAX_LEN) 40 | pr_loc_wrn("DMI field longer than %zu - restoring on module unload will be limited to that length", org_len); 41 | 42 | if(strlcpy((char *)&dmi_product_name_backup, ptr, DMI_MAX_LEN) < 0) 43 | pr_loc_wrn("Backup DMI truncated to %d", DMI_MAX_LEN); 44 | 45 | pr_loc_dbg("Saved backup DMI: %s", dmi_product_name_backup); 46 | 47 | //This TECHNICALLY can cause overflow but DMI has buffer for such a short string 48 | if (org_len < strlen_static(FW_BOARD_NAME)) 49 | pr_loc_bug("Shimmed DMI field will be longer than original!"); 50 | 51 | strcpy(ptr, FW_BOARD_NAME); 52 | } 53 | 54 | static void unpatch_dmi(void) 55 | { 56 | if (dmi_product_name_backup[0] == '\0') { 57 | pr_loc_dbg("Skipping %s - DMI not patched", __FUNCTION__); 58 | return; 59 | } 60 | 61 | strcpy((char *)dmi_get_system_info(DMI_PRODUCT_NAME), dmi_product_name_backup); 62 | pr_loc_dbg("DMI unpatched"); 63 | } 64 | 65 | int register_fw_update_shim(void) 66 | { 67 | shim_reg_in(); 68 | 69 | int out = add_blocked_execve_filename(FW_UPDATE_PATH); 70 | if (out != 0) 71 | return out; 72 | 73 | patch_dmi(); 74 | 75 | shim_reg_ok(); 76 | return 0; 77 | } 78 | 79 | int unregister_fw_update_shim(void) 80 | { 81 | shim_ureg_in(); 82 | 83 | //Do not remove execve registration here - it will be cleared in one sweep during unregister of interceptor 84 | unpatch_dmi(); 85 | 86 | shim_ureg_ok(); 87 | return 0; 88 | } -------------------------------------------------------------------------------- /shim/block_fw_update_shim.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_BLOCK_FW_UPDATE_SHIM_H 2 | #define REDPILL_BLOCK_FW_UPDATE_SHIM_H 3 | 4 | int register_fw_update_shim(void); 5 | int unregister_fw_update_shim(void); 6 | 7 | #endif //REDPILL_BLOCK_FW_UPDATE_SHIM_H 8 | -------------------------------------------------------------------------------- /shim/boot_dev/boot_shim_base.c: -------------------------------------------------------------------------------- 1 | #include "boot_shim_base.h" 2 | #include "../../common.h" 3 | #include "../../config/runtime_config.h" //struct boot_media 4 | #include "../../internal/scsi/scsi_toolbox.h" //is_sata_disk(), opportunistic_read_capacity() 5 | #include //struct scsi_device 6 | #include //struct usb_device 7 | 8 | //Definition of known VID/PIDs for USB-based shims 9 | #define SBOOT_RET_VID 0xf400 //Retail boot drive VID 10 | #define SBOOT_RET_PID 0xf400 //Retail boot drive PID 11 | #define SBOOT_MFG_VID 0xf401 //Force-reinstall boot drive VID 12 | #define SBOOT_MFG_PID 0xf401 //Force-reinstall boot drive PID 13 | 14 | void *mapped_shim_data = NULL; 15 | 16 | void set_shimmed_boot_dev(void *private_data) 17 | { 18 | mapped_shim_data = private_data; 19 | } 20 | 21 | void *get_shimmed_boot_dev(void) 22 | { 23 | return mapped_shim_data; 24 | } 25 | 26 | bool scsi_is_boot_dev_target(const struct boot_media *boot_dev_config, struct scsi_device *sdp) 27 | { 28 | if (!is_sata_disk(&sdp->sdev_gendev)) { 29 | pr_loc_dbg("%s: it's not a SATA disk, ignoring", __FUNCTION__); 30 | return false; 31 | } 32 | 33 | pr_loc_dbg("Checking if SATA disk is a shim target - id=%u channel=%u vendor=\"%s\" model=\"%s\"", sdp->id, 34 | sdp->channel, sdp->vendor, sdp->model); 35 | 36 | long long capacity_mib = opportunistic_read_capacity(sdp); 37 | if (unlikely(capacity_mib < 0)) { 38 | pr_loc_dbg("Failed to estimate drive capacity (error=%lld) - it WILL NOT be shimmed", capacity_mib); 39 | return false; 40 | } 41 | 42 | if (capacity_mib > boot_dev_config->dom_size_mib) { 43 | pr_loc_dbg("Device has capacity of ~%llu MiB - it WILL NOT be shimmed (>%lu)", capacity_mib, 44 | boot_dev_config->dom_size_mib); 45 | return false; 46 | } 47 | 48 | if (unlikely(get_shimmed_boot_dev())) { 49 | pr_loc_wrn("Boot device was already shimmed but a new matching device (~%llu MiB <= %lu) appeared again - " 50 | "this may produce unpredictable outcomes! Ignoring - check your hardware", capacity_mib, 51 | boot_dev_config->dom_size_mib); 52 | return false; 53 | } 54 | 55 | pr_loc_dbg("Device has capacity of ~%llu MiB - it is a shimmable target (<=%lu)", capacity_mib, 56 | boot_dev_config->dom_size_mib); 57 | 58 | return true; 59 | } 60 | 61 | void usb_shim_as_boot_dev(const struct boot_media *boot_dev_config, struct usb_device *usb_device) 62 | { 63 | if (boot_dev_config->mfg_mode) { 64 | usb_device->descriptor.idVendor = cpu_to_le16(SBOOT_MFG_VID); 65 | usb_device->descriptor.idProduct = cpu_to_le16(SBOOT_MFG_PID); 66 | } else { 67 | usb_device->descriptor.idVendor = cpu_to_le16(SBOOT_RET_VID); 68 | usb_device->descriptor.idProduct = cpu_to_le16(SBOOT_RET_PID); 69 | } 70 | } -------------------------------------------------------------------------------- /shim/boot_dev/boot_shim_base.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_BOOT_SHIM_BASE_H 2 | #define REDPILL_BOOT_SHIM_BASE_H 3 | 4 | #include //bool 5 | 6 | struct boot_media; 7 | struct usb_device; 8 | struct scsi_device; 9 | 10 | /** 11 | * Modify given USB device instance to conform to syno kernel boot device specification 12 | * 13 | * This function takes into consideration the boot_media configuration regarding mfg vs retail mode and will change the 14 | * device descriptors accordingly. It is safe to call this function multiple times on the same object. 15 | * 16 | * @param boot_dev_config Configuration to determine boot mode 17 | * @param usb_device Device to change 18 | */ 19 | void usb_shim_as_boot_dev(const struct boot_media *boot_dev_config, struct usb_device *usb_device); 20 | 21 | /** 22 | * Save a free-form pointer into a global storage to mark boot device as shimmed 23 | * 24 | * Other subsystems can determine if the boot device has been shimmed by calling get_shimmed_boot_dev(). However, the 25 | * data passed to this function is opaque by design and only makes sense to the submodule which originally set it. 26 | * 27 | * @param private_data Any non-null pointer or value castable to a pointer type (e.g. unsigned long number) 28 | */ 29 | void set_shimmed_boot_dev(void *private_data); 30 | 31 | /** 32 | * Shortcut to remove previously marked as shimmed boot device. It is an equivalent of simply calling set with NULL ptr. 33 | */ 34 | #define reset_shimmed_boot_dev() set_shimmed_boot_dev(NULL); 35 | 36 | /** 37 | * Gets shimmed boot device private data (if any) 38 | * 39 | * The caller should not try to interpret the value returned beyond NULL vs. non-NULL, unless the caller is the original 40 | * submodule which set the value using set_shimmed_boot_dev(). 41 | * 42 | * @return non-NULL pointer if device has been shimmed or NULL ptr if it wasn't 43 | */ 44 | void *get_shimmed_boot_dev(void); 45 | 46 | /** 47 | * Checks if a given SCSI disk can become a boot device 48 | * 49 | * To fully understand the rules and intricacies of how it is used in context you should read the file comment for the 50 | * native SATA DOM shim in shim/boot_dev/sata_boot_shim.c 51 | * 52 | * @param boot_dev_config User-controllable configuration with a threshold for considering an SCSI disk a boot device 53 | * @param sdp SCSI device which ideally should be an SCSI disk (as passing any other ones doesn't make sense) 54 | */ 55 | bool scsi_is_boot_dev_target(const struct boot_media *boot_dev_config, struct scsi_device *sdp); 56 | 57 | #endif //REDPILL_BOOT_SHIM_BASE_H 58 | -------------------------------------------------------------------------------- /shim/boot_dev/fake_sata_boot_shim.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_FAKE_SATA_BOOT_SHIM_H 2 | #define REDPILL_FAKE_SATA_BOOT_SHIM_H 3 | 4 | struct boot_media; 5 | int register_fake_sata_boot_shim(const struct boot_media *config); 6 | int unregister_fake_sata_boot_shim(void); 7 | 8 | #endif //REDPILL_FAKE_SATA_BOOT_SHIM_H 9 | -------------------------------------------------------------------------------- /shim/boot_dev/native_sata_boot_shim.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_NATIVE_SATA_BOOT_SHIM_H 2 | #define REDPILL_NATIVE_SATA_BOOT_SHIM_H 3 | 4 | struct boot_media; 5 | int register_native_sata_boot_shim(const struct boot_media *config); 6 | int unregister_native_sata_boot_shim(void); 7 | 8 | #endif //REDPILL_NATIVE_SATA_BOOT_SHIM_H 9 | -------------------------------------------------------------------------------- /shim/boot_dev/usb_boot_shim.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_USB_BOOT_SHIM_H 2 | #define REDPILL_USB_BOOT_SHIM_H 3 | 4 | struct boot_media; 5 | int register_usb_boot_shim(const struct boot_media *boot_dev_config); 6 | int unregister_usb_boot_shim(void); 7 | 8 | #endif //REDPILL_USB_BOOT_SHIM_H 9 | -------------------------------------------------------------------------------- /shim/boot_device_shim.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Boot device shim ensures that DSM assigns a proper /dev/ device to our USB stick or SATA DOM 3 | * 4 | * WHY IS THIS NEEDED? 5 | * In short the DSM has multiple types of SCSI devices (boot device, USB drive, eSATA drive etc). The boot device is 6 | * always mounted to /dev/synoboot (with respective partitions at /dev/synobootN). The determination what to place there 7 | * is made based on different factors depending on the type of device used to boot (see drivers/scsi/sd.c): 8 | * 1) Standard USB stick 9 | * - it has to be connected via a real USB port (i.e. not a fake-usb-over-sata like ESXi tries to do) 10 | * - it must have VID/PID combo of 0xf400/0xf400 11 | * - allows for normal boot when OS is installed, or triggers OS install/repair screen 12 | * 2) Force-install USB stick 13 | * - it has to be connected via a real USB port 14 | * - it must have VID/PID combo of 0xf401/0xf401 15 | * - always triggers OS install/repair screen 16 | * 3) SATA DOM (Disk-on-Module) 17 | * - kernel is compiled with SATA DOM support (NATIVE_SATA_DOM_SUPPORTED => CONFIG_SYNO_BOOT_SATA_DOM) 18 | * - is a real SATA (i.e. not SCSI/iSCSI/VirtIO) device 19 | * - has platform dependent vendor/model strings of CONFIG_SYNO_SATA_DOM_VENDOR/CONFIG_SYNO_SATA_DOM_MODEL 20 | * - has platform dependent vendor/model strings of CONFIG_SYNO_SATA_DOM_VENDOR_SECOND_SRC/CONFIG_SYNO_SATA_DOM_MODEL_SECOND_SRC 21 | * - SATA DOM *cannot* be used to force-reinstall (as there isn't an equivalent of USB's VID/PID of 0xf401/0xf401) 22 | * - restrictions of native SATA-DOM are lifted by sata_port_shim.c and fake_sata_boot_shim.c 23 | * 24 | * There are other special ones (e.g. iSCSI) which aren't supported here. These only apply to small subset of platforms. 25 | * 26 | * HOW IT WORKS? 27 | * Depending on the runtime configuration this shim will either engage USB-based shim or SATA-based one. See respective 28 | * implementations in shim/boot_dev/. 29 | * 30 | * References: 31 | * - See drivers/scsi/sd.c in Linux sources (especially sd_probe() method) 32 | */ 33 | #define SHIM_NAME "boot device router" 34 | 35 | #include "boot_device_shim.h" 36 | #include "shim_base.h" 37 | #include "../common.h" 38 | #include "../config/runtime_config.h" 39 | #include "boot_dev/usb_boot_shim.h" 40 | #include "boot_dev/fake_sata_boot_shim.h" 41 | #include "boot_dev/native_sata_boot_shim.h" 42 | 43 | #define BOOT_MEDIA_SHIM_NULL (-1) 44 | 45 | static int registered_type = BOOT_MEDIA_SHIM_NULL; 46 | int register_boot_shim(const struct boot_media *boot_dev_config) 47 | { 48 | shim_reg_in(); 49 | 50 | if (unlikely(registered_type != BOOT_MEDIA_SHIM_NULL)) { 51 | pr_loc_bug("Boot shim is already registered with type=%d", registered_type); 52 | return -EEXIST; 53 | } 54 | 55 | int out; 56 | switch (boot_dev_config->type) { 57 | case BOOT_MEDIA_USB: 58 | out = register_usb_boot_shim(boot_dev_config); 59 | break; 60 | case BOOT_MEDIA_SATA_DOM: 61 | out = register_native_sata_boot_shim(boot_dev_config); 62 | break; 63 | case BOOT_MEDIA_SATA_DISK: 64 | out = register_fake_sata_boot_shim(boot_dev_config); 65 | break; 66 | default: 67 | pr_loc_bug("Failed to %s - unknown type=%d", __FUNCTION__, boot_dev_config->type); 68 | return -EINVAL; 69 | } 70 | 71 | if (out != 0) 72 | return out; //individual shims should print what went wrong 73 | 74 | registered_type = boot_dev_config->type; 75 | 76 | shim_reg_ok(); 77 | return 0; 78 | } 79 | 80 | int unregister_boot_shim(void) 81 | { 82 | shim_ureg_in(); 83 | 84 | int out; 85 | switch (registered_type) { 86 | case BOOT_MEDIA_USB: 87 | out = unregister_usb_boot_shim(); 88 | break; 89 | case BOOT_MEDIA_SATA_DOM: 90 | out = unregister_native_sata_boot_shim(); 91 | break; 92 | case BOOT_MEDIA_SATA_DISK: 93 | out = unregister_fake_sata_boot_shim(); 94 | case BOOT_MEDIA_SHIM_NULL: 95 | pr_loc_bug("Boot shim is no registered"); 96 | return -ENOENT; 97 | default: //that cannot happen unless register_boot_shim() is broken 98 | pr_loc_bug("Failed to %s - unknown type=%d", __FUNCTION__, registered_type); 99 | return -EINVAL; 100 | } 101 | 102 | if (out != 0) 103 | return out; //individual shims should print what went wrong 104 | 105 | registered_type = BOOT_MEDIA_SHIM_NULL; 106 | 107 | shim_ureg_ok(); 108 | return 0; 109 | } -------------------------------------------------------------------------------- /shim/boot_device_shim.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILLLKM_BOOT_DEVICE_SHIM_H 2 | #define REDPILLLKM_BOOT_DEVICE_SHIM_H 3 | 4 | struct boot_media; 5 | int register_boot_shim(const struct boot_media *boot_dev_config); 6 | int unregister_boot_shim(void); 7 | 8 | #endif //REDPILLLKM_BOOT_DEVICE_SHIM_H 9 | -------------------------------------------------------------------------------- /shim/disable_exectutables.c: -------------------------------------------------------------------------------- 1 | #define SHIM_NAME "common executables disabler" 2 | 3 | #include "disable_exectutables.h" 4 | #include "shim_base.h" 5 | #include "../common.h" 6 | #include "../internal/intercept_execve.h" 7 | 8 | #define PSTORE_PATH "/usr/syno/bin/syno_pstore_collect" 9 | #define BOOTLOADER_UPDATE1_PATH "uboot_do_upd.sh" 10 | #define BOOTLOADER_UPDATE2_PATH "./uboot_do_upd.sh" 11 | #define SAS_FW_UPDATE_PATH "/tmpData/upd@te/sas_fw_upgrade_tool" 12 | 13 | int register_disable_executables_shim(void) 14 | { 15 | shim_reg_in(); 16 | 17 | int out; 18 | if ( 19 | (out = add_blocked_execve_filename(BOOTLOADER_UPDATE1_PATH)) != 0 20 | || (out = add_blocked_execve_filename(BOOTLOADER_UPDATE2_PATH)) != 0 21 | || (out = add_blocked_execve_filename(PSTORE_PATH)) != 0 22 | || (out = add_blocked_execve_filename(SAS_FW_UPDATE_PATH)) != 0 23 | ) { 24 | pr_loc_bug("Failed to disable some executables"); 25 | return out; 26 | } 27 | 28 | shim_reg_ok(); 29 | return 0; 30 | } 31 | 32 | int unregister_disable_executables_shim(void) 33 | { 34 | //noop - execve entries will be cleared in one sweep during unregister of interceptor (it's much faster this way) 35 | //this function is kept for consistency 36 | return 0; 37 | } -------------------------------------------------------------------------------- /shim/disable_exectutables.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_DISABLE_EXECTUTABLES_H 2 | #define REDPILL_DISABLE_EXECTUTABLES_H 3 | 4 | int register_disable_executables_shim(void); 5 | int unregister_disable_executables_shim(void); 6 | 7 | #endif //REDPILL_DISABLE_EXECTUTABLES_H 8 | -------------------------------------------------------------------------------- /shim/pci_shim.c: -------------------------------------------------------------------------------- 1 | #define SHIM_NAME "PCI devices" 2 | 3 | #include "pci_shim.h" 4 | #include "shim_base.h" 5 | #include "../common.h" 6 | #include "../config/vpci_types.h" //MAX_VPCI_DEVS, pci_shim_device_type 7 | #include "../config/platform_types.h" //hw_config 8 | #include "../internal/virtual_pci.h" 9 | #include 10 | 11 | unsigned int free_dev_idx = 0; 12 | static void *devices[MAX_VPCI_DEVS] = { NULL }; 13 | 14 | static struct pci_dev_descriptor *allocate_vpci_dev_dsc(void) { 15 | if (free_dev_idx >= MAX_VPCI_DEVS) { 16 | /*index has to be at max MAX_VPCI_DEVS-1*/ 17 | pr_loc_bug("No more device indexes are available (max devs: %d)", MAX_VPCI_DEVS); 18 | return ERR_PTR(-ENOMEM); 19 | } 20 | 21 | struct pci_dev_descriptor *dev_dsc; 22 | kmalloc_or_exit_ptr(dev_dsc, sizeof(struct pci_dev_descriptor)); 23 | memcpy(dev_dsc, &pci_dev_conf_default_normal_dev, sizeof(struct pci_dev_descriptor)); 24 | devices[free_dev_idx++] = dev_dsc; 25 | 26 | return dev_dsc; 27 | } 28 | #define allocate_vpci_dev_dsc_var() \ 29 | struct pci_dev_descriptor *dev_dsc = allocate_vpci_dev_dsc(); \ 30 | if (IS_ERR(dev_dsc)) return PTR_ERR(dev_dsc); 31 | 32 | static int 33 | add_vdev(struct pci_dev_descriptor *dev_dsc, unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, 34 | bool is_mf) 35 | { 36 | const struct virtual_device *vpci_vdev; 37 | 38 | if (is_mf) { 39 | vpci_vdev = vpci_add_multifunction_device(bus_no, dev_no, fn_no, dev_dsc); 40 | } else if(unlikely(fn_no != 0x00)) { 41 | //Making such config will either cause the device to not show up at all or only fn_no=0 one will show u 42 | pr_loc_bug("%s called with non-MF device but non-zero fn_no", __FUNCTION__); 43 | return -EINVAL; 44 | } else { 45 | vpci_vdev = vpci_add_single_device(bus_no, dev_no, dev_dsc); 46 | } 47 | 48 | return IS_ERR(vpci_vdev) ? PTR_ERR(vpci_vdev) : 0; 49 | } 50 | 51 | /** 52 | * Adds a fake Marvell controller 53 | * 54 | * These errors in kernlog are normal (as we don't emulate the behavior of the controller as it's not needed): 55 | * pci 0001:0a:00.0: Can't map mv9235 registers 56 | * ahci: probe of 0001:0a:00.0 failed with error -22 57 | * 58 | * @return 0 on success or -E 59 | */ 60 | static inline int 61 | vdev_add_generic_marvell_ahci(u16 dev, unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) 62 | { 63 | allocate_vpci_dev_dsc_var(); 64 | dev_dsc->vid = PCI_VENDOR_ID_MARVELL_EXT; 65 | dev_dsc->dev = dev; 66 | dev_dsc->rev_id = 0x11; //All Marvells so far use revision 11 67 | dev_dsc->class = U24_CLASS_TO_U8_CLASS(PCI_CLASS_STORAGE_SATA_AHCI); 68 | dev_dsc->subclass = U24_CLASS_TO_U8_SUBCLASS(PCI_CLASS_STORAGE_SATA_AHCI); 69 | dev_dsc->prog_if = U24_CLASS_TO_U8_PROGIF(PCI_CLASS_STORAGE_SATA_AHCI); 70 | return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf); 71 | } 72 | 73 | static int vdev_add_MARVELL_88SE9235(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) 74 | { 75 | return vdev_add_generic_marvell_ahci(0x9235, bus_no, dev_no, fn_no, is_mf); 76 | } 77 | 78 | static int vdev_add_MARVELL_88SE9215(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) 79 | { 80 | return vdev_add_generic_marvell_ahci(0x9215, bus_no, dev_no, fn_no, is_mf); 81 | } 82 | 83 | static int vdev_add_INTEL_I211(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) 84 | { 85 | allocate_vpci_dev_dsc_var(); 86 | dev_dsc->vid = PCI_VENDOR_ID_INTEL; 87 | dev_dsc->dev = 0x1539; 88 | dev_dsc->rev_id = 0x03; //Not confirmed 89 | dev_dsc->class = U16_CLASS_TO_U8_CLASS(PCI_CLASS_NETWORK_ETHERNET); 90 | dev_dsc->subclass = U16_CLASS_TO_U8_SUBCLASS(PCI_CLASS_NETWORK_ETHERNET); 91 | return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf); 92 | } 93 | 94 | static int vdev_add_INTEL_CPU_AHCI_CTRL(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) 95 | { 96 | allocate_vpci_dev_dsc_var(); 97 | dev_dsc->vid = PCI_VENDOR_ID_INTEL; 98 | dev_dsc->dev = 0x5ae3; 99 | dev_dsc->class = U24_CLASS_TO_U8_CLASS(PCI_CLASS_STORAGE_SATA_AHCI); 100 | dev_dsc->subclass = U24_CLASS_TO_U8_SUBCLASS(PCI_CLASS_STORAGE_SATA_AHCI); 101 | dev_dsc->prog_if = U24_CLASS_TO_U8_PROGIF(PCI_CLASS_STORAGE_SATA_AHCI); 102 | return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf); 103 | } 104 | 105 | //This technically should be a bridge but we don't have the info to recreate full tree 106 | static inline int 107 | vdev_add_generic_intel_pcie(u16 dev, unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) { 108 | allocate_vpci_dev_dsc_var(); 109 | dev_dsc->vid = PCI_VENDOR_ID_INTEL; 110 | dev_dsc->dev = dev; 111 | dev_dsc->class = U16_CLASS_TO_U8_CLASS(PCI_CLASS_BRIDGE_PCI); 112 | dev_dsc->subclass = U16_CLASS_TO_U8_SUBCLASS(PCI_CLASS_BRIDGE_PCI); 113 | return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf); 114 | } 115 | 116 | static int vdev_add_INTEL_CPU_PCIE_PA(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) 117 | { 118 | return vdev_add_generic_intel_pcie(0x5ad8, bus_no, dev_no, fn_no, is_mf); 119 | } 120 | 121 | static int vdev_add_INTEL_CPU_PCIE_PB(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) 122 | { 123 | return vdev_add_generic_intel_pcie(0x5ad6, bus_no, dev_no, fn_no, is_mf); 124 | } 125 | 126 | static int vdev_add_INTEL_CPU_USB_XHCI(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) 127 | { 128 | allocate_vpci_dev_dsc_var(); 129 | dev_dsc->vid = PCI_VENDOR_ID_INTEL; 130 | dev_dsc->dev = 0x5aa8; 131 | dev_dsc->class = U24_CLASS_TO_U8_CLASS(PCI_CLASS_SERIAL_USB_XHCI); 132 | dev_dsc->subclass = U24_CLASS_TO_U8_SUBCLASS(PCI_CLASS_SERIAL_USB_XHCI); 133 | dev_dsc->prog_if = U24_CLASS_TO_U8_PROGIF(PCI_CLASS_SERIAL_USB_XHCI); 134 | return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf); 135 | } 136 | 137 | static inline int 138 | vdev_add_generic_intel_io(u16 dev, unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) 139 | { 140 | allocate_vpci_dev_dsc_var(); 141 | dev_dsc->vid = PCI_VENDOR_ID_INTEL; 142 | dev_dsc->dev = dev; 143 | dev_dsc->class = U16_CLASS_TO_U8_CLASS(PCI_CLASS_SP_OTHER); 144 | dev_dsc->subclass = U16_CLASS_TO_U8_SUBCLASS(PCI_CLASS_SP_OTHER); 145 | return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf); 146 | } 147 | 148 | static int vdev_add_INTEL_CPU_I2C(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) 149 | { 150 | return vdev_add_generic_intel_io(0x5aac, bus_no, dev_no, fn_no, is_mf); 151 | } 152 | 153 | static int vdev_add_INTEL_CPU_HSUART(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) 154 | { 155 | return vdev_add_generic_intel_io(0x5abc, bus_no, dev_no, fn_no, is_mf); 156 | } 157 | 158 | static int vdev_add_INTEL_CPU_SPI(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) 159 | { 160 | return vdev_add_generic_intel_io(0x5ac6, bus_no, dev_no, fn_no, is_mf); 161 | } 162 | 163 | static int vdev_add_INTEL_CPU_SMBUS(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) 164 | { 165 | allocate_vpci_dev_dsc_var(); 166 | dev_dsc->vid = PCI_VENDOR_ID_INTEL; 167 | dev_dsc->dev = 0x5ad4; 168 | dev_dsc->class = U16_CLASS_TO_U8_CLASS(PCI_CLASS_SERIAL_SMBUS); 169 | dev_dsc->subclass = U16_CLASS_TO_U8_SUBCLASS(PCI_CLASS_SERIAL_SMBUS); 170 | 171 | return add_vdev(dev_dsc, bus_no, dev_no, fn_no, is_mf); 172 | } 173 | 174 | static int (*dev_type_handler_map[])(unsigned char bus_no, unsigned char dev_no, unsigned char fn_no, bool is_mf) = { 175 | [VPD_MARVELL_88SE9235] = vdev_add_MARVELL_88SE9235, 176 | [VPD_MARVELL_88SE9215] = vdev_add_MARVELL_88SE9215, 177 | [VPD_INTEL_I211] = vdev_add_INTEL_I211, 178 | [VPD_INTEL_CPU_AHCI_CTRL] = vdev_add_INTEL_CPU_AHCI_CTRL, 179 | [VPD_INTEL_CPU_PCIE_PA] = vdev_add_INTEL_CPU_PCIE_PA, 180 | [VPD_INTEL_CPU_PCIE_PB] = vdev_add_INTEL_CPU_PCIE_PB, 181 | [VPD_INTEL_CPU_USB_XHCI] = vdev_add_INTEL_CPU_USB_XHCI, 182 | [VPD_INTEL_CPU_I2C] = vdev_add_INTEL_CPU_I2C, 183 | [VPD_INTEL_CPU_HSUART] = vdev_add_INTEL_CPU_HSUART, 184 | [VPD_INTEL_CPU_SPI] = vdev_add_INTEL_CPU_SPI, 185 | [VPD_INTEL_CPU_SMBUS] = vdev_add_INTEL_CPU_SMBUS, 186 | }; 187 | 188 | int register_pci_shim(const struct hw_config *hw) 189 | { 190 | shim_reg_in(); 191 | 192 | pr_loc_dbg("Creating vPCI devices for %s", hw->name); 193 | int out; 194 | for (int i = 0; i < MAX_VPCI_DEVS; i++) { 195 | if (hw->pci_stubs[i].type == __VPD_TERMINATOR__) 196 | break; 197 | 198 | pr_loc_dbg("Calling %ps with B:D:F=%02x:%02x:%02x mf=%d", dev_type_handler_map[hw->pci_stubs[i].type], 199 | hw->pci_stubs[i].bus, hw->pci_stubs[i].dev, hw->pci_stubs[i].fn, 200 | hw->pci_stubs[i].multifunction ? 1 : 0); 201 | 202 | out = dev_type_handler_map[hw->pci_stubs[i].type](hw->pci_stubs[i].bus, hw->pci_stubs[i].dev, 203 | hw->pci_stubs[i].fn, hw->pci_stubs[i].multifunction); 204 | 205 | if (out != 0) { 206 | pr_loc_err("Failed to create vPCI device B:D:F=%02x:%02x:%02x - error=%d", hw->pci_stubs[i].bus, 207 | hw->pci_stubs[i].dev, hw->pci_stubs[i].fn, out); 208 | return out; 209 | } 210 | 211 | pr_loc_dbg("vPCI device %d created successfully", i+1); 212 | } 213 | 214 | shim_reg_ok(); 215 | return 0; 216 | } 217 | 218 | int unregister_pci_shim(void) 219 | { 220 | shim_ureg_in(); 221 | vpci_remove_all_devices_and_buses(); 222 | 223 | for (int i = 0; i < free_dev_idx; i++) { 224 | pr_loc_dbg("Free PCI dev %d @ %p", i, devices[i]); 225 | kfree(devices[i]); 226 | } 227 | 228 | shim_ureg_ok(); 229 | return -EIO; //vpci_remove_all_devices_and_buses has a bug - this is a canary to not forget 230 | } 231 | -------------------------------------------------------------------------------- /shim/pci_shim.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_PCI_SHIM_H 2 | #define REDPILL_PCI_SHIM_H 3 | 4 | enum pci_shim_device_type { 5 | __VPD_TERMINATOR__, 6 | VPD_MARVELL_88SE9235, //1b4b:9235 7 | VPD_MARVELL_88SE9215, //1b4b:9215 8 | VPD_INTEL_I211, //8086:1539 9 | VPD_INTEL_CPU_AHCI_CTRL, //8086:5ae3 10 | VPD_INTEL_CPU_PCIE_PA, //8086:5ad8 11 | VPD_INTEL_CPU_PCIE_PB, //8086:5ad6 12 | VPD_INTEL_CPU_USB_XHCI, //8086:5aa8 13 | VPD_INTEL_CPU_I2C, //8086:5aac 14 | VPD_INTEL_CPU_HSUART, //8086:5abc 15 | VPD_INTEL_CPU_SPI, //8086:5ac6 16 | VPD_INTEL_CPU_SMBUS, //8086:5ad4 17 | }; 18 | 19 | typedef struct hw_config hw_config_; 20 | int register_pci_shim(const struct hw_config *hw); 21 | int unregister_pci_shim(void); 22 | 23 | #endif //REDPILL_PCI_SHIM_H 24 | -------------------------------------------------------------------------------- /shim/pmu_shim.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_PMU_SHIM_H 2 | #define REDPILL_PMU_SHIM_H 3 | 4 | typedef struct hw_config hw_config_; 5 | int register_pmu_shim(const struct hw_config *hw); 6 | int unregister_pmu_shim(void); 7 | 8 | #endif //REDPILL_PMU_SHIM_H 9 | -------------------------------------------------------------------------------- /shim/shim_base.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_SHIM_BASE_H 2 | #define REDPILL_SHIM_BASE_H 3 | 4 | #define shim_reg_in() pr_loc_dbg("Registering %s shim", SHIM_NAME); 5 | #define shim_reg_ok() pr_loc_inf("Successfully registered %s shim", SHIM_NAME); 6 | #define shim_reg_already() do { \ 7 | pr_loc_bug("Called %s while %s() shim is already", __FUNCTION__, SHIM_NAME); \ 8 | return -EALREADY; \ 9 | } while(0) 10 | #define shim_ureg_in() pr_loc_dbg("Unregistering %s shim", SHIM_NAME); 11 | #define shim_ureg_ok() pr_loc_inf("Successfully unregistered %s shim", SHIM_NAME); 12 | #define shim_ureg_nreg() do { \ 13 | pr_loc_bug("Called %s() while %s shim is not registered (yet?)", __FUNCTION__, SHIM_NAME); \ 14 | return -ENXIO; \ 15 | } while(0) 16 | #define shim_reset_in() pr_loc_inf("Forcefully resetting %s shim", SHIM_NAME); 17 | #define shim_reset_ok() pr_loc_inf("Successfully reset %s", SHIM_NAME); 18 | 19 | 20 | #endif //REDPILL_SHIM_BASE_H 21 | -------------------------------------------------------------------------------- /shim/storage/sata_port_shim.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Allows for usage of SCSI-based storage devices like they were bare standard SATA ones 3 | * 4 | * WHY THIS SHIM? 5 | * Normally Linux doesn't care if something is an SCSI device or a SATA one, as SATA is a subset of SCSI (technically 6 | * speaking SATA is an interface using SCSI protocol). However, the syno-modified SCSI driver (drivers/scsi/sd.c) adds 7 | * a layer of logical disk types. These types determine what the disk actually is, so that the NAS can know what should 8 | * be done with them. 9 | * For example SYNO_DISK_USB, SYNO_DISK_SYNOBOOT, SYNO_DISK_SATA, and SYNO_DISK_ISCSI are all normally visible in the 10 | * system as /dev/sdX and are all SCSI-based drives. However, you can only use RAID on SATA drives and not on USB ones. 11 | * The "SYNO_DISK_SATA" is kind-of a catch-all type for all disks which are used for storing data, even if they're not 12 | * really SATA disks. One of the exceptions set by the sd.c driver is that if VirtIO driver is used all disks connected 13 | * via that method are treated as SYNO_DISK_SATA. Unfortunately that, very logical and useful, assumption is made ONLY 14 | * when the kernel is compiled with CONFIG_SYNO_KVMX64 (which is a special platform for VDSM). On all other platforms 15 | * disks connected to VirtIO will be slightly broken in old versions and unusable in newer ones (as their tpe is set to 16 | * SYNO_DISK_UNKNOWN). This shim brings the functionality available on CONFIG_SYNO_KVMX64 to all platforms. 17 | * In addition, it changes SAS ports to be SATA as well as syno reserves SYNO_DISK_SAS for usage with just a few FS 18 | * devices and external enclosures. 19 | * 20 | * HOW DOES IT WORK? 21 | * It simply plugs into the SCSI driver (via SCSI notifier) and waits for a new drive. When a new drive is connected it 22 | * checks if it was connected via the VirtIO driver or through a SAS card driver and changes the port type to 23 | * SYNO_PORT_TYPE_SATA, which will later force the driver to assume the drive is indeed a "SATA" drive (SYNO_DISK_SATA). 24 | * While the ports can be enumerated and changed all at once, it's safer to do it per-drive basis as drivers allow for 25 | * ports to be dynamically reconfigured and thus the type may change. This is also why we make no effort of 26 | * restoring port types after this shim is unregistered. 27 | * 28 | * References 29 | * - drivers/scsi/sd.c in Linux sources 30 | */ 31 | #include "sata_port_shim.h" 32 | #include "../shim_base.h" 33 | #include "../../common.h" 34 | #include "../../internal/scsi/scsi_toolbox.h" //scsi_force_replug() 35 | #include "../../internal/scsi/scsi_notifier.h" 36 | #include //struct scsi_device 37 | #include //struct Scsi_Host, SYNO_PORT_TYPE_* 38 | 39 | #define SHIM_NAME "SATA port emulator" 40 | #define VIRTIO_HOST_ID "Virtio SCSI HBA" 41 | 42 | /** 43 | * Checks if we should fix a given device or ignore it 44 | */ 45 | static bool is_fixable(struct scsi_device *sdp) 46 | { 47 | return sdp->host->hostt->syno_port_type == SYNO_PORT_TYPE_SAS || 48 | (sdp->host->hostt->syno_port_type != SYNO_PORT_TYPE_SATA && 49 | strcmp(sdp->host->hostt->name, VIRTIO_HOST_ID) == 0); 50 | } 51 | 52 | /** 53 | * Processes any new devices connected to the system AND existing devices which were forcefully reconnected 54 | * 55 | * When a device which is deemed fixable it will replace its port to SATA to make it work as a standard SATA drive. 56 | * 57 | * @return 0 on success, -E on error 58 | */ 59 | static int on_new_scsi_disk_device(struct scsi_device *sdp) 60 | { 61 | if (!is_fixable(sdp)) 62 | return 0; 63 | 64 | pr_loc_dbg("Found new disk vendor=\"%s\" model=\"%s\" connected to \"%s\" HBA over non-SATA port (type=%d) - " 65 | "fixing to SATA port (type=%d)", sdp->vendor, sdp->model, sdp->host->hostt->name, 66 | sdp->host->hostt->syno_port_type, SYNO_PORT_TYPE_SATA); 67 | 68 | sdp->host->hostt->syno_port_type = SYNO_PORT_TYPE_SATA; 69 | 70 | return 0; 71 | } 72 | 73 | /** 74 | * Called for every existing SCSI-based disk to determine if there are any fixable devices which are already connected 75 | * 76 | * Every device which is fixable but still connected it will be forcefully re-connected, as this is the only way to fix 77 | * existing device properly. 78 | * 79 | * @return 0 on success, -E on error 80 | */ 81 | static int on_existing_scsi_disk_device(struct scsi_device *sdp) 82 | { 83 | if (!is_fixable(sdp)) 84 | return 0; 85 | 86 | pr_loc_dbg( 87 | "Found initialized disk vendor=\"%s\" model=\"%s\" connected to \"%s\" HBA over non-SATA port (type=%d)." 88 | " It must be auto-replugged to fix it.", sdp->vendor, sdp->model, sdp->host->hostt->name, 89 | sdp->host->hostt->syno_port_type); 90 | 91 | //After that it will land in on_new_scsi_disk_device() 92 | scsi_force_replug(sdp); 93 | 94 | return 0; 95 | } 96 | 97 | /** 98 | * Tiny shim to direct SCSI notifications to on_existing_scsi_disk_device() before it's probed 99 | */ 100 | static int scsi_disk_probe_handler(struct notifier_block *self, unsigned long state, void *data) 101 | { 102 | if (state != SCSI_EVT_DEV_PROBING) 103 | return NOTIFY_DONE; 104 | 105 | on_new_scsi_disk_device(data); 106 | return NOTIFY_OK; 107 | } 108 | 109 | static struct notifier_block scsi_disk_nb = { 110 | .notifier_call = scsi_disk_probe_handler, 111 | .priority = INT_MIN, //we want to be FIRST so that we other things can get the correct drive type 112 | }; 113 | 114 | int register_sata_port_shim(void) 115 | { 116 | shim_reg_in(); 117 | 118 | int out; 119 | 120 | pr_loc_dbg("Registering for new devices notifications"); 121 | out = subscribe_scsi_disk_events(&scsi_disk_nb); 122 | if (unlikely(out != 0)) { 123 | pr_loc_err("Failed to register for SCSI disks notifications - error=%d", out); 124 | return out; 125 | } 126 | 127 | pr_loc_dbg("Iterating over existing devices"); 128 | out = for_each_scsi_disk(on_existing_scsi_disk_device); 129 | if (unlikely(out != 0 && out != -ENXIO)) { 130 | pr_loc_err("Failed to enumerate current SCSI disks - error=%d", out); 131 | return out; 132 | } 133 | 134 | shim_reg_ok(); 135 | return 0; 136 | } 137 | 138 | int unregister_sata_port_shim(void) 139 | { 140 | shim_ureg_in(); 141 | 142 | unsubscribe_scsi_disk_events(&scsi_disk_nb); 143 | 144 | shim_ureg_ok(); 145 | return 0; //noop 146 | } -------------------------------------------------------------------------------- /shim/storage/sata_port_shim.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_SATA_PORT_SHIM_H 2 | #define REDPILL_SATA_PORT_SHIM_H 3 | 4 | int register_sata_port_shim(void); 5 | int unregister_sata_port_shim(void); 6 | 7 | #endif //REDPILL_SATA_PORT_SHIM_H 8 | -------------------------------------------------------------------------------- /shim/storage/smart_shim.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_SMART_SHIM_H 2 | #define REDPILL_SMART_SHIM_H 3 | 4 | int register_disk_smart_shim(void); 5 | int unregister_disk_smart_shim(void); 6 | 7 | #endif //REDPILL_SMART_SHIM_H 8 | -------------------------------------------------------------------------------- /shim/uart_fixer.c: -------------------------------------------------------------------------------- 1 | #define SHIM_NAME "UART fixer" 2 | 3 | #include "uart_fixer.h" 4 | #include "shim_base.h" 5 | #include "../common.h" 6 | #include "../config/runtime_config.h" //STD_COM* 7 | #include "../config/platform_types.h" //hw_config 8 | #include "../internal/call_protected.h" //early_serial_setup() 9 | #include "../internal/override/override_symbol.h" //overriding uart_match_port() 10 | #include //serial8250_unregister_port 11 | 12 | #ifdef DBG_DISABLE_UART_SWAP_FIX 13 | static int noinline uart_swap_hw_output(unsigned int from, unsigned char to) 14 | { 15 | pr_loc_wrn("UART swapping needed for the platform but forcefully disabled via DBG_DISABLE_UART_SWAP"); 16 | return 0; 17 | } 18 | #elif defined(UART_BUG_SWAPPED) 19 | #include "../internal/uart/uart_swapper.h" 20 | #else 21 | static int noinline uart_swap_hw_output(unsigned int from, unsigned char to) 22 | { 23 | pr_loc_bug("Called %s from uart_fixer context when UART_BUG_SWAPPED is not set", __FUNCTION__); 24 | return -EINVAL; 25 | } 26 | #endif 27 | 28 | static bool ttyS0_force_initted = false; //Was ttyS0 forcefully initialized by us? 29 | static bool serial_swapped = false; //Whether ttyS0 and ttyS1 were swapped 30 | 31 | /** 32 | * On some platforms (e.g. 918+) the first serial port appears to not be functional as it's not initialized properly. 33 | * 34 | * 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 35 | * fixed by this function setting kernel console output to ttyS0 will result in earlycon working as expected (as it 36 | * doesn't use the normal 8250 driver) with nothing being transmitted as soon as earlycon is switched to the proper 37 | * "console=" port. 38 | */ 39 | static int fix_muted_ttyS0(void) 40 | { 41 | int out = 0; 42 | struct uart_port port = { 43 | .iobase = STD_COM1_IOBASE, 44 | .uartclk = STD_COMX_BAUD * 16, 45 | .irq = STD_COM1_IRQ, 46 | .flags = STD_COMX_FLAGS 47 | }; 48 | 49 | if ((out = _early_serial_setup(&port)) != 0) { 50 | pr_loc_err("Failed to register ttyS0 to hw port @ %lx", port.iobase); 51 | return out; 52 | } 53 | 54 | pr_loc_dbg("Fixed muted ttyS0 to hw port @ %lx", port.iobase); 55 | ttyS0_force_initted = true; 56 | return out; 57 | } 58 | 59 | /** 60 | * Reverses what fix_muted_ttyS0() did 61 | */ 62 | static int mute_ttyS0(void) 63 | { 64 | pr_loc_dbg("Re-muting ttyS0"); 65 | serial8250_unregister_port(0); 66 | 67 | return 0; 68 | } 69 | 70 | int register_uart_fixer(const hw_config_uart_fixer *hw) 71 | { 72 | shim_reg_in(); 73 | 74 | int out = 0; 75 | if ( 76 | (hw->swap_serial && (out = uart_swap_hw_output(1, 0)) != 0) || 77 | (hw->reinit_ttyS0 && (out = fix_muted_ttyS0()) != 0) 78 | ) { 79 | pr_loc_err("Failed to register UART fixer"); 80 | 81 | return out; 82 | } 83 | 84 | serial_swapped = hw->swap_serial; 85 | 86 | shim_reg_ok(); 87 | return out; 88 | } 89 | 90 | int unregister_uart_fixer(void) 91 | { 92 | shim_ureg_in(); 93 | 94 | int out = 0; 95 | if ( 96 | (serial_swapped && (out = uart_swap_hw_output(0, 1)) != 0) || 97 | (ttyS0_force_initted && (out = mute_ttyS0()) != 0) 98 | ) { 99 | pr_loc_err("Failed to unregister UART fixer"); 100 | return out; 101 | } 102 | 103 | shim_ureg_ok(); 104 | return out; 105 | } -------------------------------------------------------------------------------- /shim/uart_fixer.h: -------------------------------------------------------------------------------- 1 | #ifndef REDPILL_UART_FIXER_H 2 | #define REDPILL_UART_FIXER_H 3 | 4 | typedef struct hw_config hw_config_uart_fixer; 5 | int register_uart_fixer(const hw_config_uart_fixer *hw); 6 | int unregister_uart_fixer(void); 7 | 8 | #endif //REDPILL_UART_FIXER_H 9 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | # Dev Tools 2 | 3 | This directory contains some tools we use during development. They're not 4 | normally used with the module in any way. They're messy, buggy, quick and 5 | dirty but often helpful ;) -------------------------------------------------------------------------------- /tools/always_serial.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: always_serial.sh 3 | # 4 | # Runs Proxmox serial console. If it fails it tries again... and again... and again until it succeeds. This is very 5 | # useful when stopping and starting VMs with multiple serial ports. Without it every time you stop and start a VM you 6 | # have to go and open all serial consoles again manually. 7 | # To exit this script press Control+C twice (or if console is active Control+O and then Control+C twice). 8 | 9 | trap_cancel() { 10 | echo "Press Control+C once more terminate the process (or wait 2s for it to restart)" 11 | sleep 2 || exit 1 12 | } 13 | trap trap_cancel SIGINT SIGTERM 14 | 15 | if [[ "$#" -ne 2 ]]; then 16 | echo "Usage: $0 " 17 | exit 2 18 | fi 19 | 20 | while true; do 21 | clear 22 | echo "Started serial$2 monitor for VM=$1 at" $(date) 23 | qm terminal $1 -iface serial$2 24 | done -------------------------------------------------------------------------------- /tools/always_telnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: always_telnet.sh 3 | # 4 | # Runs telnet remote console. If it fails it tries again... and again... and again until it succeeds. This is very 5 | # useful when stopping and starting VMs with multiple serial ports on ESXi. Without it every time you stop and start a 6 | # VM you have to go and open all serial consoles again manually. 7 | # To exit this script press Control+C twice (or if console is active Control+O and then Control+C twice). 8 | 9 | trap_cancel() { 10 | echo "Press Control+C once more terminate the process (or wait 2s for it to restart)" 11 | sleep 2 || exit 1 12 | } 13 | trap trap_cancel SIGINT SIGTERM 14 | 15 | if [[ "$#" -ne 2 ]]; then 16 | echo "Usage: $0 " 17 | exit 2 18 | fi 19 | 20 | while true; do 21 | clear 22 | echo "Started telnet for $1:$2 at" $(date) 23 | telnet $1 $2 24 | sleep 0.2 25 | done -------------------------------------------------------------------------------- /tools/inject_rp_ko.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Injects RedPill LKM file into a ramdisk inside of an existing image (so you can test new LKM without constant full image 3 | # rebuild & transfer) 4 | # 5 | # Internally we use it something like this with Proxmox pointing to /dev/loop0 as the USB source: 6 | # rm redpill.ko ; wget https://buildsrv/redpill.ko ; \ 7 | # IRP_LEAVE_ATTACHED=1 ./inject_rp_ko.sh rp-3615-v6.img redpill.ko ; losetup ; \ 8 | # qm stop 101 ; sleep 1 ; qm start 101 ; qm terminal 101 -iface serial1 9 | 10 | self=${0##*/} 11 | img="$(realpath $1 2> /dev/null)" 12 | lkm="$(realpath $2 2> /dev/null)" 13 | if [ $# -ne 2 -o ! -f "$img" -o ! -f "$lkm" ] 14 | then 15 | echo "Usage: $self " 16 | exit 2 17 | fi 18 | 19 | echo "Detaching $img from all loopdevs" 20 | losetup -j "$img" | grep -E -o '^/dev/loop[0-9]+' | \ 21 | while read -r loopdev; do 22 | umount "${loopdev}p"? 2>/dev/null 23 | losetup -d "$loopdev" 24 | echo "Detached $loopdev" 25 | done 26 | 27 | losetup -j "$img" | grep -E -q '^/dev/loop[0-9]+' 28 | if [ $? -eq 0 ]; then 29 | echo "$img is still attached to some loop devs!" 30 | exit 1 31 | fi 32 | 33 | set -euo pipefail 34 | LODEV="$(losetup --show -f -P "$img")" 35 | 36 | UNIQ_BASE="$PWD/__inject_rp_$(date '+%s')" 37 | echo "Making directories in $UNIQ_BASE" 38 | TMP_MNT_DIR="$UNIQ_BASE/img-mnt" 39 | TMP_RDU_DIR="$UNIQ_BASE/rd-unpacked" 40 | mkdir -p "$TMP_MNT_DIR" 41 | mkdir -p "$TMP_RDU_DIR" 42 | 43 | echo "Mounting in $TMP_MNT_DIR" 44 | mount "${LODEV}p1" "$TMP_MNT_DIR" 45 | 46 | echo "Unpacking $TMP_MNT_DIR/rd.gz" 47 | cd "$TMP_RDU_DIR" 48 | if file "$TMP_MNT_DIR/rd.gz" | grep -q 'cpio archive'; then # special case: uncompressed rd 49 | IRP_FLAT_RD=1 50 | cat "$TMP_MNT_DIR/rd.gz" | cpio -idmv 51 | else 52 | IRP_FLAT_RD=0 53 | xz -dc < "$TMP_MNT_DIR/rd.gz" | cpio -idmv 54 | fi 55 | 56 | echo "Copying $lkm" 57 | cp "$lkm" "$TMP_RDU_DIR/usr/lib/modules/rp.ko" 58 | 59 | echo "Repacking $TMP_MNT_DIR/rd.gz" 60 | if [[ IRP_FLAT_RD -eq 1 ]]; then # special case: uncompressed rd 61 | find . 2>/dev/null | cpio -o -H newc -R root:root > "$TMP_MNT_DIR/rd.gz" 62 | else 63 | find . 2>/dev/null | cpio -o -H newc -R root:root | xz -9 --format=lzma > "$TMP_MNT_DIR/rd.gz" 64 | fi 65 | 66 | echo "Unmounting & detaching (if requested)" 67 | sync 68 | umount "$TMP_MNT_DIR" 69 | if [[ -z "${IRP_LEAVE_ATTACHED}" ]]; then 70 | losetup -d "$LODEV" 71 | fi 72 | 73 | echo "Cleaning up $UNIQ_BASE" 74 | rm -rf "$UNIQ_BASE" -------------------------------------------------------------------------------- /tools/make_all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Makes all permutations of the LKM and copies them to RedPill Load directory so that we can easily rebuild all images 3 | # Yes, it has all the paths hardcoded - change it to fit your environment. 4 | # When you are executing this script do it from the root of the LKM dir like ./tools/make_all.sh 5 | 6 | LINUX_SRC_ROOT="$PWD/.." 7 | RP_LOAD_ROOT="$HOME/build/redpill-load" 8 | 9 | set -euo pipefail 10 | rm redpill-*.bin redpill-*.ko || true 11 | 12 | # Build for v6 for 3615xs 13 | make LINUX_SRC="$LINUX_SRC_ROOT/linux-3.10.x-bromolow-25426" clean 14 | make LINUX_SRC="$LINUX_SRC_ROOT/linux-3.10.x-bromolow-25426" -j dev-v6 15 | cp redpill.ko "$RP_LOAD_ROOT/ext/rp-lkm/redpill-linux-v3.10.105.ko" 16 | cp redpill.ko redpill-v6-3615.bin 17 | 18 | # Build for v7 for 3615xs 19 | make LINUX_SRC="$LINUX_SRC_ROOT/bromolow-DSM-7.0-toolkit/build" clean 20 | make LINUX_SRC="$LINUX_SRC_ROOT/bromolow-DSM-7.0-toolkit/build" -j dev-v7 21 | cp redpill.ko "$RP_LOAD_ROOT/ext/rp-lkm/redpill-linux-v3.10.108.ko" 22 | cp redpill.ko redpill-v7-3615.bin 23 | 24 | # Build for v6 for 918+ 25 | make LINUX_SRC="$LINUX_SRC_ROOT/linux-4.4.x-apollolake-25426" clean 26 | make LINUX_SRC="$LINUX_SRC_ROOT/linux-4.4.x-apollolake-25426" -j dev-v6 27 | cp redpill.ko "$RP_LOAD_ROOT/ext/rp-lkm/redpill-linux-v4.4.59+.ko" 28 | cp redpill.ko redpill-v6-918.bin 29 | 30 | # Build for v7 for 918+ 31 | make LINUX_SRC="$LINUX_SRC_ROOT/apollolake-DS-7.0-toolkit/build" clean 32 | make LINUX_SRC="$LINUX_SRC_ROOT/apollolake-DS-7.0-toolkit/build" -j dev-v7 33 | cp redpill.ko "$RP_LOAD_ROOT/ext/rp-lkm/redpill-linux-v4.4.180+.ko" 34 | cp redpill.ko redpill-v7-918.bin 35 | 36 | ln -s redpill-v6-3615.bin redpill-v6-3615.ko 37 | ln -s redpill-v7-3615.bin redpill-v7-3615.ko 38 | ln -s redpill-v6-918.bin redpill-v6-918.ko 39 | ln -s redpill-v7-918.bin redpill-v7-918.ko 40 | 41 | echo "OK" --------------------------------------------------------------------------------