Join Us and become a Member for a Verified Badge to access private areas with the latest PS4 PKGs.
PS4 Jailbreaking       Thread starter PSXHAX       Start date Jun 6, 2017 at 2:10 AM       36      
Status
Not open for further replies.
Last fall we saw several additions to Fail0verflow's PS4 Kexec GIT including Kaslr Support, PS4 Offsets and Marcan's Updates, and following his recent Decrypted sl-config.xml.env today PlayStation 4 developer Flat_z lit up the PS4 once again adding 4.55 Kernel Port updates to Fail0verflow's Kexec Github for homebrew developers! :bananaman1:

Here's a summary of the updates from @flatz on Github.com, as follows: 4.55 kernel port and a few slight changes
magic.h (Added 4.55 offsets)
Code:
#ifdef PS4_3_55

#define kern_off_printf 0x1df550
#define kern_off_copyin 0x3b96e0
#define kern_off_copyout 0x3b9660
#define kern_off_copyinstr 0x3b9a50
#define kern_off_kmem_alloc_contig 0x337ea0
#define kern_off_kmem_free 0x33bca0
#define kern_off_pmap_extract 0x3afd70
#define kern_off_pmap_protect 0x3b1f50
#define kern_off_sched_pin 0x1ced60
#define kern_off_sched_unpin 0x1cedc0
#define kern_off_smp_rendezvous 0x1e7810
#define kern_off_smp_no_rendevous_barrier 0x1e75d0
#define kern_off_icc_query_nowait 0x3ed450
#define kern_off_kernel_map 0x196acc8
#define kern_off_sysent 0xeed880
#define kern_off_kernel_pmap_store 0x19bd628
#define kern_off_Starsha_UcodeInfo 0x1869fa0

#define kern_off_pml4pml4i 0x19bd618
#define kern_off_dmpml4i 0x19bd61c
#define kern_off_dmpdpi 0x19bd620

#elif defined PS4_3_70

#define kern_off_printf 0x1df620
#define kern_off_copyin 0x3b97d0
#define kern_off_copyout 0x3b9750
#define kern_off_copyinstr 0x3b9b40
#define kern_off_kmem_alloc_contig 0x337f70
#define kern_off_kmem_free 0x33bd70
#define kern_off_pmap_extract 0x3afe60
#define kern_off_pmap_protect 0x3b2040
#define kern_off_sched_pin 0x1cee30
#define kern_off_sched_unpin 0x1cee90
#define kern_off_smp_rendezvous 0x1e78e0
#define kern_off_smp_no_rendevous_barrier 0x1e76a0
#define kern_off_icc_query_nowait 0x3ed7f0
#define kern_off_kernel_map 0x1976cc8
#define kern_off_sysent 0xef6d90
#define kern_off_kernel_pmap_store 0x19c9628
#define kern_off_Starsha_UcodeInfo 0
#define kern_off_gpu_devid_is_9924 0x443a20
#define kern_off_gc_get_fw_info 0x44b5a0

#define kern_off_pml4pml4i 0x19c9618
#define kern_off_dmpml4i 0x19c961c
#define kern_off_dmpdpi 0x19c9620

#elif defined PS4_4_00 || PS4_4_01

#define kern_off_printf 0x347450
#define kern_off_copyin 0x286cc0
#define kern_off_copyout 0x286c40
#define kern_off_copyinstr 0x287030
#define kern_off_kmem_alloc_contig 0x275da0
#define kern_off_kmem_free 0x369580
#define kern_off_pmap_extract 0x3eeed0
#define kern_off_pmap_protect 0x3f1120
#define kern_off_sched_pin 0x1d1120
#define kern_off_sched_unpin 0x1d1180
#define kern_off_smp_rendezvous 0x34a020
#define kern_off_smp_no_rendevous_barrier 0x349de0
#define kern_off_icc_query_nowait 0x46c5a0
#define kern_off_kernel_map 0x1fe71b8
#define kern_off_sysent 0xf17790
#define kern_off_kernel_pmap_store 0x200c310
#define kern_off_Starsha_UcodeInfo 0x18dafb0

#define kern_off_pml4pml4i 0x200c300
#define kern_off_dmpml4i 0x200c304
#define kern_off_dmpdpi 0x200c308

#elif defined PS4_4_05

#define kern_off_printf 0x347580
#define kern_off_copyin 0x286df0
#define kern_off_copyout 0x286d70
#define kern_off_copyinstr 0x287160
#define kern_off_kmem_alloc_contig 0x275ed0
#define kern_off_kmem_free 0x3696b0
#define kern_off_pmap_extract 0x3ef000
#define kern_off_pmap_protect 0x3f1250
#define kern_off_sched_pin 0x1d1250
#define kern_off_sched_unpin 0x1d12B0
#define kern_off_smp_rendezvous 0x34a150
#define kern_off_smp_no_rendevous_barrier 0x349f10
#define kern_off_icc_query_nowait 0x46c6d0
#define kern_off_kernel_map 0x1fe71b8
#define kern_off_sysent 0xf17790
#define kern_off_kernel_pmap_store 0x200c310
#define kern_off_Starsha_UcodeInfo 0
#define kern_off_gpu_devid_is_9924 0x4b9030
#define kern_off_gc_get_fw_info 0x4a19a0

#define kern_off_pml4pml4i 0x200c300
#define kern_off_dmpml4i 0x200c304
#define kern_off_dmpdpi 0x200c308

#elif defined PS4_4_55

#define kern_off_printf 0x17F30
#define kern_off_copyin 0x14A890
#define kern_off_copyout 0x14A7B0
#define kern_off_copyinstr 0x14AD00
#define kern_off_kmem_alloc_contig 0x250320
#define kern_off_kmem_free 0x16EEA0
#define kern_off_pmap_extract 0x41DBC0
#define kern_off_pmap_protect 0x420310
#define kern_off_sched_pin 0x73770
#define kern_off_sched_unpin 0x73780
#define kern_off_smp_rendezvous 0xB2BB0
#define kern_off_smp_no_rendevous_barrier 0xB2970
#define kern_off_icc_query_nowait 0x808C0
#define kern_off_kernel_map 0x1B31218
#define kern_off_sysent 0x102B690
#define kern_off_kernel_pmap_store 0x21BCC38
#define kern_off_Starsha_UcodeInfo 0
#define kern_off_gpu_devid_is_9924 0x496720
#define kern_off_gc_get_fw_info 0x4A12D0

#define kern_off_pml4pml4i 0x21BCC28
#define kern_off_dmpml4i 0x21BCC2C
#define kern_off_dmpdpi 0x21BCC30

#endif
From the README.md: (sys_kexec() now use kernel's copy funcs if called directly)

PS4 kexec implementation

This repo implements a kexec()-style system call for the PS4 Orbis kernel (FreeBSD derivative). This is designed to boot a Linux kernel directly from FreeBSD.

This is not an exploit. It is useless without some mechanism of injecting code into the PS4 OS kernel.

Building

To build a kexec.bin relocatable binary using the supplied Makefile, just type make. This will also build a kexec.a archive. You can either use the binary directly, or link the archive into your own project.

If you link kexec.a with your own code, you need to supply the two symbols _start and _end in your linker script, as kernel_init() will try to remap all pages covered by that range as RWX (to make global variable accesses work). Alternatively, you can add -DDO_NOT_REMAP_RWX to CFLAGS to disable this feature, if you have already taken care of page permissions for the code.

Usage

The code is designed to be completely standalone. There is a single entry point:
Code:
int kexec_init(void *early_printf, sys_kexec_t *sys_kexec_ptr);
Simply call kexec_init(NULL, NULL). This will locate all the required kernel symbols and install the sys_kexec system call. The syscall is registered as number 153 by default (you can change this in kexec.h). The return value is 0 on success, or negative on error.

You may pass something other than NULL as early_printf. In that case, that function will be used for debug output during early symbol resolution, before printf is available.

Since PS4 3.55(?), KASLR(Kernel Address Space Layout Randomization) is enabled by default, symtab also disappears in newer kernel, we have to hardcode offsets for some symbols. Currently we use the early_printf given by user to caculate the base address of kernel, then relocate all the symbols from the kernel base. You could enable this feature like this:
Code:
make CFLAG='-DPS4_4_00 -DKASLR -DNO_SYMTAB'
If you do not want to call the syscall from userspace, you can pass the address of a function pointer as sys_kexec_ptr. kexec_init will write to it the address of sys_kexec, so you can invoke it manually (see kexec.h for its prototype and how the arguments are passed).

If you are using the standalone kexec.bin blob, then the kexec_init function is always located at offset 0, so simply call the base address of the blob. Don't forget to pass two NULL arguments (or the appropriate pointers).

The injected sys_kexec system call takes (userspace) pointers to the kernel and initramfs blobs, their sizes, and a pointer to the (null-terminated) command line string. From userspace, this looks like this:
Code:
int kexec(void *kernel_image, size_t image_size,
          void *initramfs, size_t initramfs_size,
          const char *cmdline);

// syscall() usage:
syscall(153, kernel_image, image_size, initramfs, initramfs_size, cmdline);
kexec() will load the kernel and initramfs into memory, but will not directly boot them. To boot the loaded kernel, shut down the system. This can be accomplished by pressing the power button, but can also be done more quickly and reliably from userspace with the following sequence of system calls (this kills userspace quickly but still does a controlled filesystem unmount):
Code:
int evf = syscall(540, "SceSysCoreReboot");
syscall(546, evf, 0x4000, 0);
syscall(541, evf);
// should be syscall(37, 1, 30) but only tested via kill symbol
kill(1, 30);
Note that this software should be loaded into kernel memory space. If you are running kernel code from userland mappings, you should either switch to kernel mappings or separately copy kexec.bin to a location in kernel address space. While syscalls or exploit code may run properly from userland, the shutdown hook will not, as it will be called from a different process context.

Features

kernel_init() will automatically find the Orbis OS kernel and resolve all necessary symbols to work. There are no static symbol dependencies. If DO_NOT_REMAP_RWX is not defined (the default), it will also patch pmap_protect to disable the W^X restriction.

In addition to loading the user-supplied initramfs, kexec will locate the Radeon firmware blobs inside Orbis OS, extract them, convert them to a format suitable for Linux, and append them as an additional initramfs cpio image to the existing initramfs. This avoids the need to distribute the Radeon firmware blobs.

The radeon module, when compiled into the kernel, will automatically load this firmware on boot. Note however that most typical initramfs scripts will wipe the initramfs contents while pivoting to the real system, so if you compile radeon as a module you may not be able to access the firmware after boot. To cover that case, add some code to your initramfs /init script to copy the firmware to a tmpfs mounted on the real filesystem:
Code:
# assuming real root FS is mounted on /mnt

mkdir -p /mnt/lib/firmware/radeon
mount -t tmpfs none /mnt/lib/firmware/radeon
cp /lib/firmware/radeon/* /mnt/lib/firmware/radeon/

# now switch_root to /mnt
This avoids having to permanently store copies of the Radeon firmware, which isn't really necessary for most use cases.

There is significant debug logging available, which will appear on the system UART. Most of the code relies on the kernel printf implementation, and therefore you should patch out the UART output blanker to see it. The final code that runs on the boot CPU before booting the kernel uses direct UART writes and is not affected by the blanking feature of Orbis OS.

kexec.c (Use errno codes instead of magic values)
Code:
/*
 * ps4-kexec - a kexec() implementation for Orbis OS / FreeBSD
 *
 * Copyright (C) 2015-2016 shuffle2 <[email protected]>
 * Copyright (C) 2015-2016 Hector Martin "marcan" <[email protected]>
 *
 * This code is licensed to you under the 2-clause BSD license. See the LICENSE
 * file for more information.
 */

#include "kernel.h"
#include "linux_boot.h"
#include "x86.h"
#include "kexec.h"
#include "firmware.h"
#include "string.h"

static int k_copyin(const void *uaddr, void *kaddr, size_t len)
{
    if (!uaddr || !kaddr)
        return EFAULT;
    memcpy(kaddr, uaddr, len);
    return 0;
}

static int k_copyinstr(const void *uaddr, void *kaddr, size_t len, size_t *done)
{
    const char* ustr = (const char*)uaddr;
    char* kstr = (char*)kaddr;
    size_t ret;
    if (!uaddr || !kaddr)
        return EFAULT;
    ret = strlcpy(kstr, ustr, len);
    if (ret >= len) {
        if (done)
            *done = len;
        return ENAMETOOLONG;
    } else {
        if (done)
            *done = ret + 1;
    }
    return 0;
}

int sys_kexec(void *td, struct sys_kexec_args *uap)
{
    int err = 0;
    size_t initramfs_size = uap->initramfs_size;
    void *image = NULL;
    void *initramfs = NULL;
    size_t firmware_size = 0;
    struct boot_params *bp = NULL;
    size_t cmd_line_maxlen = 0;
    char *cmd_line = NULL;

    int (*copyin)(const void *uaddr, void *kaddr, size_t len) = td ? kern.copyin : k_copyin;
    int (*copyinstr)(const void *uaddr, void *kaddr, size_t len, size_t *done) = td ? kern.copyinstr : k_copyinstr;

    kern.printf("sys_kexec invoked\n");
    kern.printf("sys_kexec(%p, %zud, %p, %zud, \"%s\")\n", uap->image,
        uap->image_size, uap->initramfs, uap->initramfs_size, uap->cmd_line);

    // Look up our shutdown hook point
    void *icc_query_nowait = kern.icc_query_nowait;
    if (!icc_query_nowait) {
        err = ENOENT;
        goto cleanup;
    }

    // Copy in kernel image
    image = kernel_alloc_contig(uap->image_size);
    if (!image) {
        kern.printf("Failed to allocate image\n");
        err = ENOMEM;
        goto cleanup;
    }
    err = copyin(uap->image, image, uap->image_size);
    if (err) {
        kern.printf("Failed to copy in image\n");
        goto cleanup;
    }

    // Copy in initramfs
    initramfs = kernel_alloc_contig(initramfs_size + FW_CPIO_SIZE);
    if (!initramfs) {
        kern.printf("Failed to allocate initramfs\n");
        err = ENOMEM;
        goto cleanup;
    }

    err = firmware_extract(((u8*)initramfs));
    if (err < 0) {
        kern.printf("Failed to extract GPU firmware - continuing anyway\n");
    } else {
        firmware_size = err;
    }

    if (initramfs_size) {
        err = copyin(uap->initramfs, initramfs + firmware_size, initramfs_size);
        if (err) {
            kern.printf("Failed to copy in initramfs\n");
            goto cleanup;
        }
    }
    initramfs_size += firmware_size;

    // Copy in cmdline
    cmd_line_maxlen = ((struct boot_params *)image)->hdr.cmdline_size + 1;
    cmd_line = kernel_alloc_contig(cmd_line_maxlen);
    if (!cmd_line) {
        kern.printf("Failed to allocate cmdline\n");
        err = ENOMEM;
        goto cleanup;
    }
    err = copyinstr(uap->cmd_line, cmd_line, cmd_line_maxlen, NULL);
    if (err) {
        kern.printf("Failed to copy in cmdline\n");
        goto cleanup;
    }
    cmd_line[cmd_line_maxlen - 1] = 0;

    kern.printf("\nkexec parameters:\n");
    kern.printf("    Kernel image size:   %zud bytes\n", uap->image_size);
    kern.printf("    Initramfs size:      %zud bytes (%zud from user)\n",
                initramfs_size, uap->initramfs_size);
    kern.printf("    Kernel command line: %s\n", cmd_line);
    kern.printf("    Kernel image buffer: %p\n", image);
    kern.printf("    Initramfs buffer:    %p\n", initramfs);

    // Allocate our boot params
    bp = kernel_alloc_contig(sizeof(*bp));
    if (!bp) {
        kern.printf("Failed to allocate bp\n");
        err = ENOMEM;
        goto cleanup;
    }

    // Initialize bp
    // TODO should probably do this from cpu_quiesce_gate, then bp doesn't
    // need to be allocated here, just placed directly into low mem
    prepare_boot_params(bp, image);

    set_nix_info(image, bp, initramfs, initramfs_size, cmd_line);

    // Hook the final ICC shutdown function
    if (!kernel_hook_install(hook_icc_query_nowait, icc_query_nowait)) {
        kern.printf("Failed to install shutdown hook\n");
        err = EINVAL;
        goto cleanup;
    }

    kern.printf("******************************************************\n");
    kern.printf("kexec successfully armed. Please shut down the system.\n");
    kern.printf("******************************************************\n\n");

    return 0;

cleanup:
    kernel_free_contig(cmd_line, cmd_line_maxlen);
    kernel_free_contig(bp, sizeof(*bp));
    kernel_free_contig(image, uap->image_size);
    kernel_free_contig(initramfs, uap->initramfs_size);
    return err;
}

int kexec_init(void *_early_printf, sys_kexec_t *sys_kexec_ptr)
{
    int rv = 0;

    // potentially needed to write early_printf
    u64 flags = intr_disable();
    u64 wp = write_protect_disable();

    if (_early_printf)
        early_printf = _early_printf;

    if (kernel_init() < 0) {
        rv = -1;
        goto cleanup;
    }

    kern.printf("Installing sys_kexec to system call #%d\n", SYS_KEXEC);
    kernel_syscall_install(SYS_KEXEC, sys_kexec, SYS_KEXEC_NARGS);
    kern.printf("kexec_init() successful\n\n");

    if (sys_kexec_ptr)
        *sys_kexec_ptr = sys_kexec;

cleanup:
    write_protect_restore(wp);
    if (kern.sched_unpin && wp & CR0_WP) {
        // If we're returning to a state with WP enabled, assume the caller
        // wants the thread unpinned. Else the caller is expected to
        // call kern.sched_unpin() manually.
        kern.sched_unpin();
    }
    intr_restore(flags);
    return rv;
}
types.h
Code:
/*
 * ps4-kexec - a kexec() implementation for Orbis OS / FreeBSD
 *
 * Copyright (C) 2015-2016 shuffle2 <[email protected]>
 * Copyright (C) 2015-2016 Hector Martin "marcan" <[email protected]>
 *
 * This code is licensed to you under the 2-clause BSD license. See the LICENSE
 * file for more information.
 */

#ifndef TYPES_H
#define TYPES_H

typedef signed char s8;
typedef signed short s16;
typedef signed int s32;
typedef signed long long s64;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;
#ifndef TESTING
typedef u8 uint8_t;
typedef u64 size_t;
typedef s64 ssize_t;
typedef u64 uintptr_t;
typedef s64 off_t;
#endif

#define NULL ((void *)0)

#define CAT_(x, y) x ## y
#define CAT(x, y) CAT_(x, y)

#define OPAD(size) u8 CAT(_pad_, __COUNTER__)[size]
#define OSTRUCT(name, size) struct name { union { OPAD(size);
#define OSTRUCT_END };};
#define OFIELD(off, field) struct { OPAD(off); field; }

#define ASSERT_STRSIZE(struc, size) \
    _Static_assert(sizeof( struc ) == (size), "size of " #struc " != " #size )

#define offsetof(type, member)  __builtin_offsetof (type, member)

#define ENOENT 2
#define ENOMEM 12
#define EFAULT 14
#define EINVAL 22
#define ENAMETOOLONG 63

#endif
Makefile (Compiler toolchain prefix could be specified)
Code:
TOOLCHAIN_PREFIX ?=
CC = $(TOOLCHAIN_PREFIX)gcc
AR = $(TOOLCHAIN_PREFIX)ar
OBJCOPY = $(TOOLCHAIN_PREFIX)objcopy

CFLAGS=$(CFLAG)
CFLAGS += -march=btver2 -masm=intel -std=gnu11 -ffreestanding -fno-common \
    -fPIE -pie -fomit-frame-pointer -nostdlib -nostdinc \
    -fno-asynchronous-unwind-tables \
    -Os -Wall -Werror -Wl,--build-id=none,-T,kexec.ld,--nmagic \
    -mcmodel=small -mno-red-zone

SOURCES := kernel.c kexec.c linux_boot.c linux_thunk.S uart.c firmware.c \
    acpi.c crc32.c

OBJS := $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(SOURCES)))
DEPS := $(OBJS) $(SOURCES) $(INCLUDES:%=$(INC_DIR)/%) Makefile kexec.ld

all: libkexec.a kexec.bin

%.o: %.c *.h
    $(CC) -c $(CFLAGS) -o $@ $<

%.o: %.S
    $(CC) -c $(CFLAGS) -o $@ $<

libkexec.a: $(OBJS)
    $(AR) -rc $@ $(OBJS)

kexec.elf: libkexec.a kexec.ld
    $(CC) $(CFLAGS) -o $@ libkexec.a

%.bin: %.elf
    $(OBJCOPY) -O binary $< $@

clean:
    rm -f libkexec.a kexec.elf kexec.bin $(OBJS)
kernel.c (Moved setting of early_printf to kernel_init(); fixed formatting bugs)
Code:
/*
 * ps4-kexec - a kexec() implementation for Orbis OS / FreeBSD
 *
 * Copyright (C) 2015-2016 shuffle2 <[email protected]>
 * Copyright (C) 2015-2016 Hector Martin "marcan" <[email protected]>
 *
 * This code is licensed to you under the 2-clause BSD license. See the LICENSE
 * file for more information.
 */

#include "kernel.h"
#include "string.h"
#include "elf.h"
#include "x86.h"
#include "magic.h"

struct ksym_t kern;
int (*early_printf)(const char *fmt, ...) = NULL;

#define eprintf(...) do { if (early_printf) early_printf(__VA_ARGS__); } while(0)

#ifdef NO_SYMTAB

#define RESOLVE_NOERR(name) do { \
    if (kern_off_ ## name == 0) { \
        kern.name = 0; \
    } else { \
        kern.name = (void *)(kern.kern_base + kern_off_ ## name); \
    } \
} while (0);

#define RESOLVE(name) do { \
    if (kern_off_ ## name == 0) { \
        return 0; \
    } \
    RESOLVE_NOERR(name) \
} while (0);

#else

#define KERNSIZE    0x2000000

static const u8 ELF_IDENT[9] = "\x7f" "ELF\x02\x01\x01\x09\x00";
static Elf64_Sym *symtab;
static char *strtab;
static size_t strtab_size;

static Elf64_Ehdr *find_kern_ehdr(void)
{
    // Search for the kernel copy embedded in ubios, then follow it to see
    // where it was relocated to
    for (uintptr_t p = kern.kern_base; p < kern.kern_base + KERNSIZE; p += PAGE_SIZE) {
        Elf64_Ehdr *ehdr = (Elf64_Ehdr *)p;
        if (!memcmp(ehdr->e_ident, ELF_IDENT, sizeof(ELF_IDENT))) {
            for (size_t i = 0; i < ehdr->e_phnum; i++) {
                Elf64_Phdr *phdr = (Elf64_Phdr *)(p + ehdr->e_phoff) + i;
                if (phdr->p_type == PT_PHDR) {
                    return (Elf64_Ehdr *)(phdr->p_vaddr - ehdr->e_phoff);
                }
            }
        }
    }
    return NULL;
}

static Elf64_Dyn *elf_get_dyn(Elf64_Ehdr *ehdr)
{
    Elf64_Phdr *phdr = (Elf64_Phdr *)((uintptr_t)ehdr + ehdr->e_phoff);
    for (size_t i = 0; i < ehdr->e_phnum; i++, phdr++) {
        if (phdr->p_type == PT_DYNAMIC) {
            return (Elf64_Dyn *)phdr->p_vaddr;
        }
    }
    return NULL;
}

static int elf_parse_dyn(Elf64_Dyn *dyn)
{
    for (Elf64_Dyn *dp = dyn; dp->d_tag != DT_NULL; dp++) {
        switch (dp->d_tag) {
            case DT_SYMTAB:
                symtab = (Elf64_Sym *)dp->d_un.d_ptr;
                break;
            case DT_STRTAB:
                strtab = (char *)dp->d_un.d_ptr;
                break;
            case DT_STRSZ:
                strtab_size = dp->d_un.d_val;
                break;
        }
    }
    return symtab && strtab && strtab_size;
}

void *kernel_resolve(const char *name)
{
    for (Elf64_Sym *sym = symtab; (uintptr_t)(sym + 1) < (uintptr_t)strtab; sym++) {
        if (!strcmp(name, &strtab[sym->st_name])) {
            eprintf("kern.%s = %p\n", name, (void*)sym->st_value);
            return (void *)sym->st_value;
        }
    }
    eprintf("Failed to resolve symbol '%s'\n", name);
    return NULL;
}

#define RESOLVE_NOERR(name) (kern.name = kernel_resolve(#name))
#define RESOLVE(name) if (!RESOLVE_NOERR(name)) return 0;

#endif

static int resolve_symbols(void)
{
    RESOLVE(printf);
    early_printf = kern.printf;
    RESOLVE(copyin);
    RESOLVE(copyout);
    RESOLVE(copyinstr);
    RESOLVE(kernel_map);
    RESOLVE(kernel_pmap_store);
    RESOLVE(kmem_alloc_contig);
    RESOLVE(kmem_free);
    RESOLVE(pmap_extract);
    RESOLVE(pmap_protect);
    RESOLVE(sysent);
    RESOLVE(sched_pin);
    RESOLVE(sched_unpin);
    RESOLVE(smp_rendezvous);
    RESOLVE(smp_no_rendevous_barrier);
    RESOLVE(icc_query_nowait);
    RESOLVE_NOERR(Starsha_UcodeInfo);
    RESOLVE_NOERR(gpu_devid_is_9924);
    RESOLVE_NOERR(gc_get_fw_info);
    return 1;
}

#define    M_WAITOK 0x0002
#define    M_ZERO   0x0100

#define    VM_MEMATTR_DEFAULT        0x06

void *kernel_alloc_contig(size_t size)
{
    // use kmem_alloc_contig instead of contigalloc to avoid messing with a malloc_type...
    vm_offset_t ret = kern.kmem_alloc_contig(
        *kern.kernel_map, size, M_ZERO | M_WAITOK, (vm_paddr_t)0,
        ~(vm_paddr_t)0, 1, 0, VM_MEMATTR_DEFAULT);

    if (!ret) {
        kern.printf("Failed to allocate %zud bytes\n", size);
        return NULL;
    }
    return (void *)PA_TO_DM(kern.pmap_extract(kern.kernel_pmap_store, ret));
}

void kernel_free_contig(void *addr, size_t size)
{
    if (!addr)
        return;
    kern.kmem_free(*kern.kernel_map, (vm_offset_t)addr, size);
}

int kernel_hook_install(void *target, void *hook)
{
    uintptr_t t = (uintptr_t)target; // addr to redirect to
    uintptr_t h = (uintptr_t)hook; // place to write the thunk

    if (!hook || !target) {
        return 0;
    }

    kern.printf("kernel_hook_install(%p, %p)\n", target, hook);

    if (!(t & (1L << 63))) {
        kern.printf("\n===================== WARNING =====================\n");
        kern.printf("hook target function address: %p\n", target);
        kern.printf("It looks like we're running from userland memory.\n");
        kern.printf("Please run this code from a kernel memory mapping.\n\n");
        return 0;
    }
    s64 displacement = t - (h + 5);

    kern.sched_pin();
    u64 wp = write_protect_disable();
    if (displacement < -0x80000000 || displacement > 0x7fffffff) {
        kern.printf("  Using 64bit absolute jump\n");
        struct __attribute__((packed)) jmp_t{
            u8 op[2];
            s32 zero;
            void *target;
        } jmp = {
            .op = { 0xff, 0x25 },
            .zero = 0,
            .target = target,
        };
        ASSERT_STRSIZE(struct jmp_t, 14);
        memcpy(hook, &jmp, sizeof(jmp));
    } else {
        kern.printf("  Using 32bit relative jump\n");
        struct __attribute__((packed)) jmp_t{
            u8 op[1];
            s32 imm;
        } jmp = {
            .op = { 0xe9 },
            .imm = displacement,
        };
        ASSERT_STRSIZE(struct jmp_t, 5);
        memcpy(hook, &jmp, sizeof(jmp));
    }
    wbinvd();
    write_protect_restore(wp);
    kern.sched_unpin();

    return 1;
}

void kernel_syscall_install(int num, void *call, int narg)
{
    struct sysent_t *sy = &kern.sysent[num];

    kern.sched_pin();
    u64 wp = write_protect_disable();

    memset(sy, 0, sizeof(*sy));
    sy->sy_narg = narg;
    sy->sy_call = call;
    sy->sy_thrcnt = 1;

    write_protect_restore(wp);
    kern.sched_unpin();
}

void kernel_remap(void *start, void *end, int perm)
{
    u64 s = ((u64)start) & ~(u64)(PAGE_SIZE-1);
    u64 e = ((u64)end + PAGE_SIZE - 1) & ~(u64)(PAGE_SIZE-1);

    kern.printf("pmap_protect(pmap, %p, %p, %d)\n", (void*)s, (void*)e, perm);
    kern.pmap_protect(kern.kernel_pmap_store, s, e, perm);
}

static volatile int _global_test = 0;

#ifndef DO_NOT_REMAP_RWX
extern u8 _start[], _end[];

static int patch_pmap_check(void)
{
    u8 *p;

    for (p = (u8*)kern.pmap_protect;
         p < ((u8*)kern.pmap_protect + 0x500); p++) {
        if (!memcmp(p, "\x83\xe0\x06\x83\xf8\x06", 6)) {
            p[2] = 0;
            kern.printf("pmap_protect patch successful (found at %p)\n", p);
            return 1;
        }
    }
    kern.printf("pmap_protect patch failed!\n");
    return 0;
}
#endif

int kernel_init(void *_early_printf)
{
    int rv = -1;

    if (_early_printf)
        early_printf = _early_printf;

    eprintf("kernel_init()\n");

#ifdef KASLR
    // use `early_printf` to calculate kernel base
    if (early_printf == NULL)
        return 0;

    kern.kern_base = (u64)(early_printf - kern_off_printf);
    if ((kern.kern_base & PAGE_MASK) != 0) {
        eprintf("Kernel base is not aligned\n");
        return 0;
    } else {
        eprintf("Kernel base = %llx\n", kern.kern_base);
    }

    u64 DMPML4I = *(u32 *)(kern.kern_base + kern_off_dmpml4i);
    u64 DMPDPI = *(u32 *)(kern.kern_base + kern_off_dmpdpi);

#else
    kern.kern_base = KVADDR(0x1ff, 0x1fe, 0, 0); // 0xffffffff80000000

    u64 DMPML4I = 0x1fc;
    u64 DMPDPI = 0;
#endif

    kern.dmap_base = KVADDR(DMPML4I, DMPDPI, 0, 0);
    eprintf("Direct map base = %llx\n", kern.dmap_base);

    // We may not be mapped writable yet, so to be able to write to globals
    // we need WP disabled.
    u64 flags = intr_disable();
    u64 wp = write_protect_disable();

#ifndef NO_SYMTAB
    Elf64_Ehdr *ehdr = find_kern_ehdr();
    if (!ehdr) {
        eprintf("Could not find kernel ELF header\n");
        goto err;
    }
    eprintf("ELF header at %p\n", ehdr);

    Elf64_Dyn *dyn = elf_get_dyn(ehdr);
    if (!dyn) {
        eprintf("Could not find kernel dynamic header\n");
        goto err;
    }
    eprintf("ELF dynamic section at %p\n", dyn);

    if (!elf_parse_dyn(dyn)) {
        eprintf("Failed to parse ELF dynamic section\n");
        goto err;
    }
#endif

    if (!resolve_symbols()) {
        eprintf("Failed to resolve all symbols\n");
        goto err;
    }

    // Pin ourselves as soon as possible. This is expected to be released by the caller.
    kern.sched_pin();

#ifndef DO_NOT_REMAP_RWX
    if (!patch_pmap_check())
        goto err;
#endif

#ifndef DO_NOT_REMAP_RWX
    // kernel_remap may need interrupts, but may not write to globals!
    enable_interrupts();
    kernel_remap(_start, _end, 7);
    disable_interrupts();
#endif

    // Writing to globals is now safe.

    kern.printf("Testing global variable access (write protection)...\n");
    _global_test = 1;
    kern.printf("OK.\n");

    kern.printf("Kernel interface initialized\n");
    rv = 0;

err:
    write_protect_restore(wp);
    intr_restore(flags);
    return rv;
}
kernel.h
Code:
/*
 * ps4-kexec - a kexec() implementation for Orbis OS / FreeBSD
 *
 * Copyright (C) 2015-2016 shuffle2 <[email protected]>
 * Copyright (C) 2015-2016 Hector Martin "marcan" <[email protected]>
 *
 * This code is licensed to you under the 2-clause BSD license. See the LICENSE
 * file for more information.
 */

#ifndef KERNEL_H
#define KERNEL_H

#include "types.h"

#define PAGE_SIZE 0x4000
#define PAGE_MASK (PAGE_SIZE - 1)

#define PML4SHIFT 39
#define PDPSHIFT 30
#define PDRSHIFT 21
#define PAGE_SHIFT 12

#define KVADDR(l4, l3, l2, l1) ( \
    ((unsigned long)-1 << 47) | \
    ((unsigned long)(l4) << PML4SHIFT) | \
    ((unsigned long)(l3) << PDPSHIFT) | \
    ((unsigned long)(l2) << PDRSHIFT) | \
    ((unsigned long)(l1) << PAGE_SHIFT))

#define PA_TO_DM(x) (((uintptr_t)x) | kern.dmap_base)
#define DM_TO_ID(x) (((uintptr_t)x) & (~kern.dmap_base)) // XXX

typedef u64 vm_paddr_t;
typedef u64 vm_offset_t;
typedef u64 vm_size_t;
typedef void * vm_map_t;
typedef char vm_memattr_t;
typedef void * pmap_t;

typedef void (*smp_rendezvous_callback_t)(void *);

struct sysent_t {
    int sy_narg;
    void *sy_call;
    u16 sy_auevent;
    void *sy_systrace_args_func;
    int sy_entry;
    int sy_return;
    int sy_flags;
    int sy_thrcnt;
};

struct ksym_t {
    // two parameters related to kaslr (they are not symbols)
    uintptr_t kern_base;
    uintptr_t dmap_base;

    int (*printf)(const char *fmt, ...);

    int (*copyin)(const void *uaddr, void *kaddr, size_t len);
    int (*copyout)(const void *kaddr, void *uaddr, size_t len);
    int (*copyinstr)(const void *uaddr, void *kaddr, size_t len, size_t *done);

    void **kernel_map;
    void *kernel_pmap_store;
    vm_offset_t (*kmem_alloc_contig)(vm_map_t map, vm_size_t size, int flags,
                                     vm_paddr_t low, vm_paddr_t high,
                                     unsigned long alignment,
                                     unsigned long boundary,
                                     vm_memattr_t memattr);
    void (*kmem_free)(vm_map_t, vm_offset_t, vm_size_t);
    vm_paddr_t (*pmap_extract)(pmap_t pmap, vm_offset_t va);
    void (*pmap_protect)(pmap_t pmap, u64 sva, u64 eva, u8 pr);

    struct sysent_t *sysent;

    void (*sched_pin)(void);
    void (*sched_unpin)(void);
    void (*smp_rendezvous)(smp_rendezvous_callback_t,
                           smp_rendezvous_callback_t,
                           smp_rendezvous_callback_t, void *);
    // yes...it is misspelled :)
    void (*smp_no_rendevous_barrier)(void *);
    void *icc_query_nowait;
    void *Starsha_UcodeInfo;
    int (*gpu_devid_is_9924)();
    void *(*gc_get_fw_info)();
};

extern struct ksym_t kern;

static inline int curcpu(void)
{
    int cpuid;
    // TODO ensure offsetof(struct pcpu, pc_cpuid) == 0x34 on all fw
    asm volatile("mov %0, gs:0x34;" : "=r" (cpuid));
    return cpuid;
}

// Assign a working printf function to this to debug the symbol resolver
extern int (*early_printf)(const char *fmt, ...);

void *kernel_resolve(const char *name);

void *kernel_alloc_contig(size_t size);
void kernel_free_contig(void *addr, size_t size);

void kernel_remap(void *start, void *end, int perm);

void kernel_syscall_install(int num, void *call, int narg);
int kernel_hook_install(void *target, void *hook);

int kernel_init(void *early_printf);

#endif
kexec.c
Code:
/*
 * ps4-kexec - a kexec() implementation for Orbis OS / FreeBSD
 *
 * Copyright (C) 2015-2016 shuffle2 <[email protected]>
 * Copyright (C) 2015-2016 Hector Martin "marcan" <[email protected]>
 *
 * This code is licensed to you under the 2-clause BSD license. See the LICENSE
 * file for more information.
 */

#include "kernel.h"
#include "linux_boot.h"
#include "x86.h"
#include "kexec.h"
#include "firmware.h"
#include "string.h"

static int k_copyin(const void *uaddr, void *kaddr, size_t len)
{
    if (!uaddr || !kaddr)
        return EFAULT;
    memcpy(kaddr, uaddr, len);
    return 0;
}

static int k_copyinstr(const void *uaddr, void *kaddr, size_t len, size_t *done)
{
    const char *ustr = (const char*)uaddr;
    char *kstr = (char*)kaddr;
    size_t ret;
    if (!uaddr || !kaddr)
        return EFAULT;
    ret = strlcpy(kstr, ustr, len);
    if (ret >= len) {
        if (done)
            *done = len;
        return ENAMETOOLONG;
    } else {
        if (done)
            *done = ret + 1;
    }
    return 0;
}

int sys_kexec(void *td, struct sys_kexec_args *uap)
{
    int err = 0;
    size_t initramfs_size = uap->initramfs_size;
    void *image = NULL;
    void *initramfs = NULL;
    size_t firmware_size = 0;
    struct boot_params *bp = NULL;
    size_t cmd_line_maxlen = 0;
    char *cmd_line = NULL;

    int (*copyin)(const void *uaddr, void *kaddr, size_t len) = td ? kern.copyin : k_copyin;
    int (*copyinstr)(const void *uaddr, void *kaddr, size_t len, size_t *done) = td ? kern.copyinstr : k_copyinstr;

    kern.printf("sys_kexec invoked\n");
    kern.printf("sys_kexec(%p, %zu, %p, %zu, \"%s\")\n", uap->image,
        uap->image_size, uap->initramfs, uap->initramfs_size, uap->cmd_line);

    // Look up our shutdown hook point
    void *icc_query_nowait = kern.icc_query_nowait;
    if (!icc_query_nowait) {
        err = ENOENT;
        goto cleanup;
    }

    // Copy in kernel image
    image = kernel_alloc_contig(uap->image_size);
    if (!image) {
        kern.printf("Failed to allocate image\n");
        err = ENOMEM;
        goto cleanup;
    }
    err = copyin(uap->image, image, uap->image_size);
    if (err) {
        kern.printf("Failed to copy in image\n");
        goto cleanup;
    }

    // Copy in initramfs
    initramfs = kernel_alloc_contig(initramfs_size + FW_CPIO_SIZE);
    if (!initramfs) {
        kern.printf("Failed to allocate initramfs\n");
        err = ENOMEM;
        goto cleanup;
    }

    err = firmware_extract(((u8*)initramfs));
    if (err < 0) {
        kern.printf("Failed to extract GPU firmware - continuing anyway\n");
    } else {
        firmware_size = err;
    }

    if (initramfs_size) {
        err = copyin(uap->initramfs, initramfs + firmware_size, initramfs_size);
        if (err) {
            kern.printf("Failed to copy in initramfs\n");
            goto cleanup;
        }
    }
    initramfs_size += firmware_size;

    // Copy in cmdline
    cmd_line_maxlen = ((struct boot_params *)image)->hdr.cmdline_size + 1;
    cmd_line = kernel_alloc_contig(cmd_line_maxlen);
    if (!cmd_line) {
        kern.printf("Failed to allocate cmdline\n");
        err = ENOMEM;
        goto cleanup;
    }
    err = copyinstr(uap->cmd_line, cmd_line, cmd_line_maxlen, NULL);
    if (err) {
        kern.printf("Failed to copy in cmdline\n");
        goto cleanup;
    }
    cmd_line[cmd_line_maxlen - 1] = 0;

    kern.printf("\nkexec parameters:\n");
    kern.printf("    Kernel image size:   %zu bytes\n", uap->image_size);
    kern.printf("    Initramfs size:      %zu bytes (%zu from user)\n",
                initramfs_size, uap->initramfs_size);
    kern.printf("    Kernel command line: %s\n", cmd_line);
    kern.printf("    Kernel image buffer: %p\n", image);
    kern.printf("    Initramfs buffer:    %p\n", initramfs);

    // Allocate our boot params
    bp = kernel_alloc_contig(sizeof(*bp));
    if (!bp) {
        kern.printf("Failed to allocate bp\n");
        err = ENOMEM;
        goto cleanup;
    }

    // Initialize bp
    // TODO should probably do this from cpu_quiesce_gate, then bp doesn't
    // need to be allocated here, just placed directly into low mem
    prepare_boot_params(bp, image);

    set_nix_info(image, bp, initramfs, initramfs_size, cmd_line);

    // Hook the final ICC shutdown function
    if (!kernel_hook_install(hook_icc_query_nowait, icc_query_nowait)) {
        kern.printf("Failed to install shutdown hook\n");
        err = EINVAL;
        goto cleanup;
    }

    kern.printf("******************************************************\n");
    kern.printf("kexec successfully armed. Please shut down the system.\n");
    kern.printf("******************************************************\n\n");

    return 0;

cleanup:
    kernel_free_contig(cmd_line, cmd_line_maxlen);
    kernel_free_contig(bp, sizeof(*bp));
    kernel_free_contig(image, uap->image_size);
    kernel_free_contig(initramfs, uap->initramfs_size);
    return err;
}

int kexec_init(void *_early_printf, sys_kexec_t *sys_kexec_ptr)
{
    int rv = 0;

    // potentially needed to write early_printf
    u64 flags = intr_disable();
    u64 wp = write_protect_disable();

    if (kernel_init(_early_printf) < 0) {
        rv = -1;
        goto cleanup;
    }

    kern.printf("Installing sys_kexec to system call #%d\n", SYS_KEXEC);
    kernel_syscall_install(SYS_KEXEC, sys_kexec, SYS_KEXEC_NARGS);
    kern.printf("kexec_init() successful\n\n");

    if (sys_kexec_ptr)
        *sys_kexec_ptr = sys_kexec;

cleanup:
    write_protect_restore(wp);
    if (kern.sched_unpin && wp & CR0_WP) {
        // If we're returning to a state with WP enabled, assume the caller
        // wants the thread unpinned. Else the caller is expected to
        // call kern.sched_unpin() manually.
        kern.sched_unpin();
    }
    intr_restore(flags);
    return rv;
}
kexec.h
Code:
/*
 * ps4-kexec - a kexec() implementation for Orbis OS / FreeBSD
 *
 * Copyright (C) 2015-2016 shuffle2 <[email protected]>
 * Copyright (C) 2015-2016 Hector Martin "marcan" <[email protected]>
 *
 * This code is licensed to you under the 2-clause BSD license. See the LICENSE
 * file for more information.
 */

#ifndef KEXEC_H
#define KEXEC_H

#include "types.h"

#define SYS_KEXEC 153
#define SYS_KEXEC_NARGS 5

struct sys_kexec_args {
    void *image;
    size_t image_size;
    void *initramfs;
    size_t initramfs_size;
    char *cmd_line;
};

typedef int (*sys_kexec_t)(void *td, struct sys_kexec_args *uap);

// Note: td is unused, you can pass NULL if you call this directly.
int sys_kexec(void *td, struct sys_kexec_args *uap);

int kernel_init(void *early_printf);

int kexec_init(void *early_printf, sys_kexec_t *sys_kexec_ptr)
    __attribute__ ((section (".init")));

#endif
Cheers to @defense in the PSXHAX Shoutbox for passing along the news! <3
PS4 4.55 Kernel Port Updates by Flatz to Fail0verflow Kexec Github.jpg
 

Comments

Status
Not open for further replies.
Back
Top