/*
 * pi3's Linux kernel Runtime Guard
 *
 * Component:
 *  - Exploit detection main module
 *
 * Notes:
 *  - None
 *
 * Timeline:
 *  - Created: 06.IX.2017
 *
 * Author:
 *  - Adam 'pi3' Zabrocki (http://pi3.com.pl)
 *
 */

#ifndef P_EXPLOIT_DETECTION_MAIN_H
#define P_EXPLOIT_DETECTION_MAIN_H

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0) && defined(CONFIG_ARCH_HAS_SYSCALL_WRAPPER)

  /*
   * Linux kernel 4.17.xx introduced some changes which broke LKRG (and not only LKRG).
   * More information about it can be found here:
   *
   * https://lists.gt.net/linux/kernel/2952784
   * https://github.com/torvalds/linux/commit/d5a00528b58cdb2c71206e18bd021e34c4eab878
   *
   * In short, Linux kernel changed the exported names for syscalls (critical change
   * from the LKRG perspective). Currently, one syscall is generating up to 4 stubs
   * (depends on compilation option), e.g. sys_waitid and compat_sys_waitid can be now:
   *
   * 810f2080 T __x64_sys_waitid           # x64 64-bit-ptregs   -> C stub
   * 810f20b0 T __ia32_sys_waitid          # ia32 32-bit-ptregs  -> C stub[*]
   * 810f2470 T __ia32_compat_sys_waitid   # ia32 32-bit-ptregs  -> compat C stub
   * 810f2490 T __x32_compat_sys_waitid    # x32 64-bit-ptregs   -> compat C stub
   *
   *  [*] This stub is often unused - depends on the syscall
   *
   * Example from my Ubuntu VM (kernel 4.17.8) for sys_execve():
   *
   * ffffffffa1a9b9d0 T __ia32_compat_sys_execve
   * ffffffffa1a9ba90 T __ia32_sys_execve [*]
   * ffffffffa1a9bb30 T __x32_compat_sys_execve
   * ffffffffa1a9bb80 T __x64_sys_execve
   *
   *  [*] Unused
   *
   * But at the same time sys_setuid() can be as follow:
   *
   * ffffffffa44a69d0 T __sys_setuid  # NOT a syscall but some of the stubs can wrap it
   * ffffffffa44a6ad0 T __x64_sys_setuid
   * ffffffffa44a6af0 T __ia32_sys_setuid   [*]
   * ffffffffa452b0d0 T __x64_sys_setuid16
   * ffffffffa452b100 T __ia32_sys_setuid16
   *
   *  [*] Used
   *
   * CONFIG_X86_X32 is covered here.
   */

 #ifdef CONFIG_X86_64

  #define P_GET_IA32_SYSCALL_NAME(x) P_IA32_SYSCALL_PREFIX(x)

  #define P_SYSCALL_PREFIX(x) P_TO_STRING(__x64_sys_ ## x)
  #define P_IA32_SYSCALL_PREFIX(x) P_TO_STRING(__ia32_sys_ ## x)
  #define P_GET_SET_ID_PREFIX(x) P_TO_STRING(__sys_ ## x)

 #elif defined(CONFIG_X86_32) && LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)

  /*
   * Since Linux kernel 5.7 SYSCALL_WRAPPER's "magic" was backported to x86 (32 bits)
   * arch as well. Let's correctly handle it here. More about this "magic" can be
   * found here:
   *
   * https://lore.kernel.org/lkml/20200313195144.164260-10-brgerst@gmail.com/T/
   */

  #define P_GET_IA32_SYSCALL_NAME(x) P_IA32_SYSCALL_PREFIX(x)

  #define P_SYSCALL_PREFIX(x) P_TO_STRING(__ia32_sys_ ## x)
  #define P_IA32_SYSCALL_PREFIX(x) P_TO_STRING(__ia32_sys_ ## x)
  #define P_GET_SET_ID_PREFIX(x) P_TO_STRING(__sys_ ## x)

 #elif defined(CONFIG_ARM64)

  #define P_GET_ARM64_SYSCALL_NAME(x) P_ARM64_SYSCALL_PREFIX(x)

  #define P_SYSCALL_PREFIX(x) P_TO_STRING(__arm64_sys_ ## x)
  #define P_GET_SET_ID_PREFIX(x) P_TO_STRING(__sys_ ## x)

 #else
  #define P_SYSCALL_PREFIX(x) P_TO_STRING(sys_ ## x)
  #define P_GET_SET_ID_PREFIX(x) P_SYSCALL_PREFIX(x)
 #endif
#else
 #define P_SYSCALL_PREFIX(x) P_TO_STRING(sys_ ## x)
 #define P_GET_SET_ID_PREFIX(x) P_SYSCALL_PREFIX(x)
#endif

#define P_TO_STRING(x) # x
#define P_GET_SYSCALL_NAME(x) P_SYSCALL_PREFIX(x)
#define P_GET_SET_ID_NAME(x) P_GET_SET_ID_PREFIX(x)

#define P_ED_PROCESS_OFF_MAX 0x1000

#define p_ed_alloc_valid()      kmem_cache_alloc(p_ed_wq_valid_cache, GFP_ATOMIC)
#define p_ed_free_valid(name)   kmem_cache_free(p_ed_wq_valid_cache, (void *)(name))


static inline unsigned int p_get_task_state(struct task_struct *p_task) {

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0) || \
  (defined(RHEL_RELEASE_CODE) && RHEL_RELEASE_CODE > RHEL_RELEASE_VERSION(8,5))
   return READ_ONCE(p_task->__state);
#elif defined(ACCESS_ONCE) && !defined(READ_ONCE)
   return ACCESS_ONCE(p_task->state);
#else
   return READ_ONCE(p_task->state);
#endif

}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0)
/*
 * It's temporary fix - redefine what can't be imported.
 * TODO: Need to research better way of inspecting SELinux variables!
 */
/* Policy capabilities */
enum {
    POLICYDB_CAPABILITY_NETPEER,
    POLICYDB_CAPABILITY_OPENPERM,
    POLICYDB_CAPABILITY_EXTSOCKCLASS,
    POLICYDB_CAPABILITY_ALWAYSNETWORK,
    POLICYDB_CAPABILITY_CGROUPSECLABEL,
    POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION,
    __POLICYDB_CAPABILITY_MAX
};

struct p_selinux_state {
#if defined(CONFIG_SECURITY_SELINUX_DISABLE) || LINUX_VERSION_CODE < KERNEL_VERSION(5,6,0)
    bool disabled;
#endif
#ifdef P_SELINUX_VERIFY
    bool enforcing;
#endif
    bool checkreqprot;
    bool initialized;
    bool policycap[__POLICYDB_CAPABILITY_MAX];
    struct selinux_avc *avc;
    struct selinux_ss *ss;
};
#endif

#if !defined(CONFIG_STACKTRACE) || \
    (defined(CONFIG_ARCH_STACKWALK) && (!defined(RHEL_RELEASE_CODE) || RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(9, 0)))
struct stack_trace {
    unsigned int nr_entries, max_entries;
    unsigned long *entries;
    int skip;
};
#endif

struct p_cred {

    kuid_t uid;                      /* real UID of the task */
    kgid_t gid;                      /* real GID of the task */
    kuid_t suid;                     /* saved UID of the task */
    kgid_t sgid;                     /* saved GID of the task */
    kuid_t euid;                     /* effective UID of the task */
    kgid_t egid;                     /* effective GID of the task */
    kuid_t fsuid;                    /* UID for VFS ops */
    kgid_t fsgid;                    /* GID for VFS ops */
    struct user_namespace *user_ns;  /* user_ns the caps and keyrings are relative to. */

};

struct p_seccomp {

   struct seccomp sec;
   int flag;
   int flag_sync_thread;

};

#ifdef P_LKRG_TASK_OFF_DEBUG
 #define P_PCFI_STACK_BUF     0x150
#else
 #define P_PCFI_STACK_BUF     0x200
#endif

#ifdef CONFIG_X86_64
 #define P_NORMALIZE_LONG 0x0101010101010101
 #define P_MASK_COUNTER   0x07FFFFFFFFFFFFFF
#else
 #define P_NORMALIZE_LONG 0x01010101
 #define P_MASK_COUNTER   0x07FFFFFF
#endif

#ifdef P_LKRG_TASK_OFF_DEBUG
#define P_LKRG_TASK_OFF_MAXBUF 256

struct p_task_off_debug {

   unsigned int p_caller;
   unsigned int p_action;
   unsigned long p_old_off;
   unsigned int p_debug_val;
   /* only for override_* API */
   struct stack_trace p_trace;
   char p_internal_buf[P_PCFI_STACK_BUF];

};
#endif

/* X86(-64)*/
#if defined(CONFIG_X86) && LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0)
#ifndef CONFIG_PAX_RANDKSTACK
 #define P_VERIFY_ADDR_LIMIT 1
#endif
/* ARM(64) */
#elif (defined(CONFIG_ARM) && LINUX_VERSION_CODE < KERNEL_VERSION(5,15,0)) \
   || (defined(CONFIG_ARM64) && LINUX_VERSION_CODE < KERNEL_VERSION(5,11,0))
 #define P_VERIFY_ADDR_LIMIT 2
#endif

struct p_ed_process_task {

   unsigned long p_off;
   struct task_struct *p_task;
   pid_t p_pid;
   char p_comm[TASK_COMM_LEN+1];
   const struct cred *p_real_cred_ptr;
   struct p_cred p_real_cred;
#if defined(CONFIG_SECCOMP)
   struct p_seccomp p_sec;
#endif
#if defined(P_VERIFY_ADDR_LIMIT)
   mm_segment_t p_addr_limit;
#endif
   struct nsproxy *p_nsproxy;
   struct nsproxy p_ns;
   void *p_stack;
   unsigned int p_off_count;
#ifdef P_LKRG_TASK_OFF_DEBUG
   struct p_task_off_debug p_off_debug[P_LKRG_TASK_OFF_MAXBUF];
   unsigned int p_off_counter;
   int p_off_debug_cnt;
#endif

};

#ifdef CONFIG_SECURITY_SELINUX
struct p_ed_guard_selinux {

#ifdef P_SELINUX_VERIFY
   int p_selinux_enforcing;
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0)
   int p_selinux_enabled;
#endif

};
#endif

struct p_ed_global_variables {

#ifdef CONFIG_SECURITY_SELINUX
   p_lkrg_counter_lock p_selinux_lock;
   struct p_ed_guard_selinux p_selinux;
#endif

};

static inline void p_crash_task_if_current(struct task_struct *p_task);

static inline void p_kill_task_by_task(struct task_struct *p_task) {

   p_print_log(P_LOG_ALERT, "BLOCK: Task: Killing pid %u, name %s",
               task_pid_nr(p_task), p_task->comm);

   send_sig_info(SIGKILL, SEND_SIG_PRIV, p_task);

   p_crash_task_if_current(p_task);
}

#include "ed_task_tree.h"
#include "syscalls/p_install.h"
#include "syscalls/exec/p_security_bprm_committing_creds/p_security_bprm_committing_creds.h"
#include "syscalls/exec/p_security_bprm_committed_creds/p_security_bprm_committed_creds.h"
#include "syscalls/p_call_usermodehelper/p_usermode_kernel_dep.h"
#include "syscalls/p_call_usermodehelper/p_call_usermodehelper.h"
#include "syscalls/p_call_usermodehelper_exec/p_call_usermodehelper_exec.h"
#include "syscalls/p_wake_up_new_task/p_wake_up_new_task.h"
#include "syscalls/p_do_exit/p_do_exit.h"
#include "syscalls/p_sys_setuid/p_sys_setuid.h"
#include "syscalls/p_sys_setreuid/p_sys_setreuid.h"
#include "syscalls/p_sys_setresuid/p_sys_setresuid.h"
#include "syscalls/p_sys_setfsuid/p_sys_setfsuid.h"
#include "syscalls/p_sys_setgid/p_sys_setgid.h"
#include "syscalls/p_sys_setregid/p_sys_setregid.h"
#include "syscalls/p_sys_setresgid/p_sys_setresgid.h"
#include "syscalls/p_sys_setfsgid/p_sys_setfsgid.h"
#include "syscalls/p_generic_permission/p_generic_permission.h"
#include "syscalls/p_sel_write_enforce/p_sel_write_enforce.h"
#include "syscalls/p_seccomp/p_seccomp.h"
#include "syscalls/p_sys_unshare/p_sys_unshare.h"
#include "syscalls/p_security_ptrace_access/p_security_ptrace_access.h"
/* Override creds */
#include "syscalls/override/p_override_creds/p_override_creds.h"
#include "syscalls/override/p_revert_creds/p_revert_creds.h"
/* Namespaces */
#include "syscalls/p_sys_setns/p_sys_setns.h"
/* OverlayFS */
#include "syscalls/override/overlayfs/p_ovl_override_sync/p_ovl_override_sync.h"
/* pCFI */
#include "syscalls/pCFI/p_mark_inode_dirty/p_mark_inode_dirty.h"
#include "syscalls/pCFI/p_schedule/p_schedule.h"
#include "syscalls/pCFI/p___queue_work/p___queue_work.h"
#include "syscalls/pCFI/p_lookup_fast/p_lookup_fast.h"
#include "syscalls/p_capable/p_capable.h"
#include "syscalls/p_scm_send/p_scm_send.h"

extern struct p_ed_global_variables p_ed_guard_globals;
extern unsigned long p_pcfi_CPU_flags;

void p_dump_task_f(void *p_arg);

void p_verify_addr_limit(struct p_ed_process *p_orig);
void p_update_ed_process(struct p_ed_process *p_source, char p_stack);
void p_set_ed_process_on(struct p_ed_process *p_source);
void p_set_ed_process_off(struct p_ed_process *p_source);
void p_ed_is_off_off_wrap(struct p_ed_process *p_source);
void p_ed_validate_off_flag_wrap(struct p_ed_process *p_source);
/* For override */
void p_set_ed_process_override_on(struct p_ed_process *p_source);
void p_set_ed_process_override_off(struct p_ed_process *p_source);
#if P_OVL_OVERRIDE_SYNC_MODE
/* For OverlayFS */
int p_verify_ovl_override_sync(struct p_ed_process *p_source);
#endif

void p_ed_find_validate_current_locked(void);
void p_ed_find_validate_current(void);
void p_ed_validate_current(struct p_ed_process *p_source);
void p_ed_enforce_validation(void);
unsigned int p_ed_enforce_validation_paranoid(void);
int p_ed_enforce_pcfi(struct task_struct *p_task, struct p_ed_process *p_orig, struct pt_regs *p_regs);
int p_ed_pcfi_validate_sp(struct task_struct *p_task, struct p_ed_process *p_orig, unsigned long p_sp);

int p_exploit_detection_init(void);
void p_exploit_detection_exit(void);

int p_selinux_state_init(void);
void p_selinux_state_restore(void);
void p_selinux_state_update(void);
int p_selinux_state_changed(void);
int p_selinux_state_enforcing(void);

#ifdef P_LKRG_TASK_OFF_DEBUG
void p_debug_off_flag_off(struct p_ed_process *p_source, unsigned int p_id);
void p_debug_off_flag_on(struct p_ed_process *p_source, unsigned int p_id);
void p_debug_off_flag_reset(struct p_ed_process *p_source, unsigned int p_id);
void p_debug_off_flag_override_off(struct p_ed_process *p_source, unsigned int p_id, struct pt_regs *p_regs);
void p_debug_off_flag_override_on(struct p_ed_process *p_source, unsigned int p_id, struct pt_regs *p_regs);
#endif

static inline unsigned int p_is_ed_task(struct task_struct *p_task) {

   return p_task->mm && !is_global_init(p_task);
}

static inline void p_ed_kill_task(struct p_ed_process *p_source) {

   struct task_struct *p_task = p_source->p_ed_task.p_task;

   switch (P_CTRL(p_pint_enforce)) {

      /* Panic */
      case 2:
         // OK, we need to crash the kernel now
         p_panic("Task: Unexpected changes for pid %u, name %s",
            task_pid_nr(p_task), p_task->comm);
         break;

      /* Kill task */
      case 1:
         /* Zero kill_task_on_unlock to kill upon unlocking the ed task lock */
         p_source->kill_task_on_unlock = 0;
         break;

      /* Log and accept */
      case 0:
         p_update_ed_process(p_source, 1);
         p_print_log(P_LOG_ALERT, "ALLOW: Task: Accepting unexpected changes for pid %u, name %s",
            task_pid_nr(p_task), p_task->comm);
         break;

   }
}

static inline void p_pcfi_kill_edp_or_task(struct p_ed_process *p_source, struct task_struct *p_task) {

   if (p_source)
      p_task = p_source->p_ed_task.p_task;

   switch (P_CTRL(p_pcfi_enforce)) {

      /* Panic */
      case 2:
         // OK, we need to crash the kernel now
         p_panic("Task: pCFI validation failed for pid %u, name %s",
            task_pid_nr(p_task), p_task->comm);
         break;

      /* Kill task */
      case 1:
         if (p_source) {
            /* Zero kill_task_on_unlock to kill upon unlocking the ed task lock */
            p_source->kill_task_on_unlock = 0;
         } else {
            p_kill_task_by_task(p_task);
         }
         break;

      /* Log */
      case 0:
         p_print_log(P_LOG_ALERT, "ALLOW: Task: pCFI validation failed for pid %u, name %s",
            task_pid_nr(p_task), p_task->comm);
         break;

   }
}

static inline void p_pcfi_kill_ed_task(struct p_ed_process *p_source) {

   p_pcfi_kill_edp_or_task(p_source, NULL);
}

/*
 * First CPU specific code
 */
#ifdef CONFIG_X86
 #include "arch/x86/p_ed_x86_arch.h"
#elif defined(CONFIG_ARM)
 #include "arch/arm/p_ed_arm_arch.h"
#elif defined(CONFIG_ARM64)
 #include "arch/arm64/p_ed_arm64_arch.h"
#else
 #error "!!! UNSUPPORTED ARCHITECTURE !!!"
#endif

static inline unsigned int p_ed_pcfi_cpu(unsigned char p_kill) {

#ifdef CONFIG_X86
   unsigned int p_ret;

   p_ret = p_ed_pcfi_x86_validate_wp(p_kill);
   p_ret += p_ed_pcfi_x86_validate_smXp(p_kill);

   return p_ret;

#elif defined(CONFIG_ARM)

   return 0;

#elif defined(CONFIG_ARM64)

   return 0;

#endif

}

#include "../wrap/p_struct_wrap.h"

static inline void p_crash_task_if_current(struct task_struct *p_task) {

   unsigned long flags;

   /*
    * Unfortunately, we cannot just take the sighand lock for another task
    * without risking deadlock.  See __lock_task_sighand() in the kernel.
    */
   if (p_task != current)
      return;

   /*
    * We assume we also send SIGKILL via send_sig_info(), but a rootkit may
    * easily intercept that, so we redundantly enter SIGKILL into the pending
    * signals mask directly.  For similar reasons, as well as to potentially
    * limit what the currently running syscall will do, zero some rlimits -
    * those known to be relevant yet safe to zero (unlike e.g. RLIMIT_FSIZE,
    * which may be unsafe to zero in a privileged victim task).
    */
   spin_lock_irqsave(&p_task->sighand->siglock, flags);
   p_task->signal->rlim[RLIMIT_NPROC].rlim_cur = p_task->signal->rlim[RLIMIT_NPROC].rlim_max = 0;
   p_task->flags |= PF_NPROC_EXCEEDED; /* safe read-modify-write assuming p_task == current */
   p_task->signal->rlim[RLIMIT_NOFILE].rlim_cur = p_task->signal->rlim[RLIMIT_NOFILE].rlim_max = 0;
   p_task->signal->rlim[RLIMIT_AS].rlim_cur = p_task->signal->rlim[RLIMIT_AS].rlim_max = 0;
   sigaddset(&p_task->pending.signal, SIGKILL);
   spin_unlock_irqrestore(&p_task->sighand->siglock, flags);

   /*
    * The task shouldn't ever return to userspace due to pending SIGKILL, but
    * if it does then let's (hopefully) crash it (although it may avoid that by
    * trapping SIGSEGV).  This is just an added layer of protection in case the
    * SIGKILL doesn't work for some (malicious) reason.
    */
   p_regs_set_ip(task_pt_regs(p_task), -1);
}

#endif
