/*
 * 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)
 *
 */

#include "../../p_lkrg_main.h"

struct p_ed_global_variables p_ed_guard_globals;
static unsigned long p_global_off_cookie;
static unsigned long p_global_cnt_cookie;
struct kmem_cache *p_ed_wq_valid_cache = NULL;
static struct kmem_cache *p_ed_pcfi_cache = NULL;

unsigned long p_pcfi_CPU_flags;

#define p_ed_pcfi_alloc()      kmem_cache_alloc(p_ed_pcfi_cache, GFP_ATOMIC)
#define p_ed_pcfi_free(name)   kmem_cache_free(p_ed_pcfi_cache, (void *)(name))

static int p_cmp_tasks(struct p_ed_process *p_orig, char p_kill);
static unsigned int p_iterate_lkrg_tasks_paranoid(void);

static const struct p_functions_hooks {

   const char *name;
   int (*install)(int p_isra);
   void (*uninstall)(void);
   int p_fatal;
   const char *p_error_message;
   int is_isra_safe;

} p_functions_hooks_array[] = {
   {
     "security_bprm_committing_creds",
     p_install_security_bprm_committing_creds_hook,
     p_uninstall_security_bprm_committing_creds_hook,
     1,
     NULL,
     1
   },
   {
     "security_bprm_committed_creds",
     p_install_security_bprm_committed_creds_hook,
     p_uninstall_security_bprm_committed_creds_hook,
     1,
     NULL,
     1
   },
   { "call_usermodehelper",
     p_install_call_usermodehelper_hook,
     p_uninstall_call_usermodehelper_hook,
     0,
     "Won't guard usermodehelper",
     0
   },
   { "call_usermodehelper_exec",
     p_install_call_usermodehelper_exec_hook,
     p_uninstall_call_usermodehelper_exec_hook,
     0,
     "Won't enforce validation on 'call_usermodehelper_exec'",
     1
   },
   { "wake_up_new_task",
     p_install_wake_up_new_task_hook,
     p_uninstall_wake_up_new_task_hook,
     1,
     NULL,
     0
   },
   { "do_exit",
     p_install_do_exit_hook,
     p_uninstall_do_exit_hook,
     1,
     NULL,
     1
   },
   { "security_ptrace_access",
     p_install_security_ptrace_access_hook,
     p_uninstall_security_ptrace_access_hook,
     0,
     "Won't enforce validation on 'security_ptrace_access'",
     0
   },
   { "sys_setuid",
     p_install_sys_setuid_hook,
     p_uninstall_sys_setuid_hook,
     1,
     NULL,
     0
   },
   { "sys_setreuid",
     p_install_sys_setreuid_hook,
     p_uninstall_sys_setreuid_hook,
     1,
     NULL,
     0
   },
   { "sys_setresuid",
     p_install_sys_setresuid_hook,
     p_uninstall_sys_setresuid_hook,
     1,
     NULL,
     0
   },
   { "sys_setfsuid",
     p_install_sys_setfsuid_hook,
     p_uninstall_sys_setfsuid_hook,
     1,
     NULL,
     0
   },
   { "sys_setgid",
     p_install_sys_setgid_hook,
     p_uninstall_sys_setgid_hook,
     1,
     NULL,
     0
   },
   { "sys_setregid",
     p_install_sys_setregid_hook,
     p_uninstall_sys_setregid_hook,
     1,
     NULL,
     0
   },
   { "sys_setresgid",
     p_install_sys_setresgid_hook,
     p_uninstall_sys_setresgid_hook,
     1,
     NULL,
     0
   },
   { "sys_setfsgid",
     p_install_sys_setfsgid_hook,
     p_uninstall_sys_setfsgid_hook,
     1,
     NULL,
     0
   },
   { "generic_permission",
     p_install_generic_permission_hook,
     p_uninstall_generic_permission_hook,
     0,
     "Won't enforce validation on 'generic_permission'",
     1
   },
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
   { "sel_write_enforce",
     p_install_sel_write_enforce_hook,
     p_uninstall_sel_write_enforce_hook,
     1,
     NULL,
     1
   },
#endif
#if defined(CONFIG_SECCOMP)
   { "seccomp",
     p_install_seccomp_hook,
     p_uninstall_seccomp_hook,
     1,
     NULL,
     1
   },
#endif
   { "sys_unshare",
     p_install_sys_unshare_hook,
     p_uninstall_sys_unshare_hook,
     1,
     NULL,
     1
   },
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,13,0)
   { "override_creds",
     p_install_override_creds_hook,
     p_uninstall_override_creds_hook,
     1,
     NULL,
     0
   },
   { "revert_creds",
     p_install_revert_creds_hook,
     p_uninstall_revert_creds_hook,
     1,
     NULL,
     0
   },
#endif
   /* Namespaces. */
   { "sys_setns",
     p_install_sys_setns_hook,
     p_uninstall_sys_setns_hook,
     1,
     NULL,
     0
   },
#if P_OVL_OVERRIDE_SYNC_MODE
   /* OverlayFS
    *
    * OverlayFS might not be installed in that system - it is not critical
    * scenario. If OverlayFS is installed, used but not found (unlikely)
    * in worst case, we might have FP. Continue...
    */
   {
     P_OVL_OVERRIDE_SYNC_FUNC,
     p_install_ovl_override_sync_hook,
     p_uninstall_ovl_override_sync_hook,
     0,
     "Can't hook '" P_OVL_OVERRIDE_SYNC_FUNC "'. This is expected when OverlayFS is not used.",
     1
   },
#endif
   /* pCFI */
   { "pcfi_mark_inode_dirty",
     p_install_pcfi_mark_inode_dirty_hook,
     p_uninstall_pcfi_mark_inode_dirty_hook,
     0,
     "Won't enforce pCFI validation on 'mark_inode_dirty'",
     0
   },
   { "pcfi_schedule",
     p_install_pcfi_schedule_hook,
     p_uninstall_pcfi_schedule_hook,
     0,
     "Won't enforce pCFI validation on 'schedule'",
     1
   },
   { "pcfi___queue_work",
     p_install_pcfi___queue_work_hook,
     p_uninstall_pcfi___queue_work_hook,
     0,
     "Won't enforce validation on '__queue_work'",
     1
   },
   { "pcfi_lookup_fast",
     p_install_pcfi_lookup_fast_hook,
     p_uninstall_pcfi_lookup_fast_hook,
     0,
     "Won't enforce pCFI validation on 'lookup_fast'",
     1
   },
   { "capable",
     p_install_capable_hook,
     p_uninstall_capable_hook,
     0,
     "Won't enforce validation on 'capable'",
     1
   },
   { "scm_send",
     p_install_scm_send_hook,
     p_uninstall_scm_send_hook,
     0,
     "Won't enforce validation on 'scm_send'",
     1
   },
   { NULL, NULL, NULL, 1, NULL, 0 }
};

static void p_ed_wq_valid_cache_zero(void *p_arg) {

   struct work_struct *p_struct = p_arg;

   memset(p_struct, 0, sizeof(struct work_struct));
}

static int p_ed_wq_valid_cache_init(void) {

   if ( (p_ed_wq_valid_cache = kmem_cache_create("p_ed_wq_valid_cache", sizeof(struct work_struct),
                                                 0, P_LKRG_CACHE_FLAGS, p_ed_wq_valid_cache_zero)) == NULL) {
      return P_LKRG_GENERAL_ERROR;
   }

   return P_LKRG_SUCCESS;
}

static void p_ed_wq_valid_cache_delete(void) {

#ifdef flush_workqueue
/*
 * When flush_workqueue became a macro, it started emitting warnings and the
 * below function was introduced in place of the original function.  We should
 * avoid flushing a system queue, but meanwhile we just suppress the warnings.
 */
   __flush_workqueue(system_unbound_wq);
#else
   flush_workqueue(system_unbound_wq);
#endif
   if (p_ed_wq_valid_cache) {
      kmem_cache_destroy(p_ed_wq_valid_cache);
      p_ed_wq_valid_cache = NULL;
   }
}

static void p_dump_creds(struct p_cred *p_where, const struct cred *p_from) {

   /* Track process's IDs */
   p_set_uid(&p_where->uid, p_get_uid(&p_from->uid));
   p_set_gid(&p_where->gid, p_get_gid(&p_from->gid));
   p_set_uid(&p_where->suid, p_get_uid(&p_from->suid));
   p_set_gid(&p_where->sgid, p_get_gid(&p_from->sgid));
   p_set_uid(&p_where->euid, p_get_uid(&p_from->euid));
   p_set_gid(&p_where->egid, p_get_gid(&p_from->egid));
   p_set_uid(&p_where->fsuid, p_get_uid(&p_from->fsuid));
   p_set_gid(&p_where->fsgid, p_get_gid(&p_from->fsgid));

   p_where->user_ns  = p_from->user_ns;
}

#if defined(CONFIG_SECCOMP)
static void p_dump_seccomp(struct p_seccomp *p_sec, struct task_struct *p_task) {

   p_sec->sec.mode    = p_task->seccomp.mode;   // Mode
   p_sec->sec.filter  = p_task->seccomp.filter; // Filter
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,11,0)
   if (test_task_syscall_work(p_task,SECCOMP))
#else
   if (test_tsk_thread_flag(p_task,TIF_SECCOMP))
#endif
      p_sec->flag = 1;
   else
      p_sec->flag = 0;
}
#endif

#if defined(P_VERIFY_ADDR_LIMIT)
static inline unsigned long p_get_addr_limit(struct task_struct *p_task) {

/* X86(-64)*/
#if defined(CONFIG_X86)

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0) && LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0)

   return p_task->thread.addr_limit.seg;

#elif LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0)

   struct thread_info *p_ti = task_thread_info(p_task);

   return p_ti->addr_limit.seg;

#endif

/* ARM(64) */
#elif defined(CONFIG_ARM) || defined(CONFIG_ARM64)

   struct thread_info *p_ti = task_thread_info(p_task);

   return p_ti->addr_limit;

#endif
}
#endif

void p_verify_addr_limit(struct p_ed_process *p_orig) {

#if defined(P_VERIFY_ADDR_LIMIT)

   unsigned long p_addr_limit =
#if defined(CONFIG_X86)
                                 p_orig->p_ed_task.p_addr_limit.seg;
#elif defined(CONFIG_ARM) || defined(CONFIG_ARM64)
                                 p_orig->p_ed_task.p_addr_limit;
#endif
   struct task_struct *p_current = p_orig->p_ed_task.p_task;

   /* Verify addr_limit */
   if (unlikely(p_addr_limit != p_get_addr_limit(p_current))) {
      p_print_log(P_LOG_ALERT,
         "DETECT: Task: addr_limit corruption (expected 0x%lx vs. actual 0x%lx) for pid %u, name %s",
         p_addr_limit, p_get_addr_limit(p_current), task_pid_nr(p_current), p_current->comm);
      dump_stack();
      // kill this process!
      p_ed_kill_task(p_orig);
   }

#endif
}

#if defined(P_VERIFY_ADDR_LIMIT)
static inline void p_dump_addr_limit(mm_segment_t *p_addr_limit, struct task_struct *p_task) {
#if defined(CONFIG_X86)
   p_addr_limit->seg =
#elif defined(CONFIG_ARM) || defined(CONFIG_ARM64)
   *p_addr_limit =
#endif
                       p_get_addr_limit(p_task);
}
#endif

void p_update_ed_process(struct p_ed_process *p_source, char p_stack) {

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

   p_print_log(P_LOG_WATCH, "Updating pid %u", p_task->pid);

   rcu_read_lock();
   /* Track process's metadata */
   p_source->p_ed_task.p_pid                      = p_task->pid;
   p_source->p_ed_task.p_real_cred_ptr            = rcu_dereference(p_task->real_cred);
   if (p_stack)
      p_source->p_ed_task.p_stack                 = p_task->stack;
   /* Namespaces */
   /*
    * A comment in <linux/nsproxy.h> documents "the namespaces access rules",
    * which allow reading current task's namespaces without precautions, but
    * another task's with task_lock() and check for "nsproxy != NULL".  We also
    * worry about potential deadlocks, so we use spin_trylock() instead and
    * skip our namespace validation if we couldn't acquire a lock.  We do the
    * same here (update) and again further down this source file (validation).
    */
   if (p_task == current || spin_trylock(&p_task->alloc_lock)) {
      p_source->p_ed_task.p_nsproxy               = p_task->nsproxy;
      if (p_source->p_ed_task.p_nsproxy) {
         p_source->p_ed_task.p_ns.uts_ns          = p_task->nsproxy->uts_ns;
         p_source->p_ed_task.p_ns.ipc_ns          = p_task->nsproxy->ipc_ns;
         p_source->p_ed_task.p_ns.mnt_ns          = p_task->nsproxy->mnt_ns;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,11,0)
         p_source->p_ed_task.p_ns.pid_ns_for_children = p_task->nsproxy->pid_ns_for_children;
#else
         p_source->p_ed_task.p_ns.pid_ns          = p_task->nsproxy->pid_ns;
#endif
         p_source->p_ed_task.p_ns.net_ns          = p_task->nsproxy->net_ns;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0)
         p_source->p_ed_task.p_ns.cgroup_ns       = p_task->nsproxy->cgroup_ns;
#endif
      }
      if (p_task != current)
         spin_unlock(&p_task->alloc_lock);
   } else
      p_source->p_ed_task.p_nsproxy = NULL;
   /* Creds */
   p_dump_creds(&p_source->p_ed_task.p_real_cred, p_source->p_ed_task.p_real_cred_ptr);
#if defined(CONFIG_SECCOMP)
   /* Seccomp */
   p_dump_seccomp(&p_source->p_ed_task.p_sec, p_task);
#endif
   /* addr_limit */
#if defined(P_VERIFY_ADDR_LIMIT)
   p_dump_addr_limit(&p_source->p_ed_task.p_addr_limit, p_task);
#endif
   /* Name */
   strncpy(p_source->p_ed_task.p_comm, p_task->comm, TASK_COMM_LEN);
   p_source->p_ed_task.p_comm[TASK_COMM_LEN] = 0;
   rcu_read_unlock();
}

#ifdef P_LKRG_TASK_OFF_DEBUG
static const struct {

   char p_id;
   const char *p_name;

} p_debug_off_flag_callers[] = {

   { 30, "p_ovl_override_sync_ret" },
   { 31, "p_override_creds_entry" },
   { 32, "p_revert_creds_ret" },
   { 33, "p_seccomp_entry" },
   { 34, "p_seccomp_ret" },
   { 35, "p_seccomp_entry (TSYNC child)" },
   { 36, "p_seccomp_ret (TSYNC child)" },
   { 39, "p_security_bprm_committing_creds_entry" },
   { 40, "p_security_bprm_committed_creds_ret" },
   { 41, "p_sys_setfsgid_entry" },
   { 42, "p_sys_setfsgid_ret" },
   { 43, "p_sys_setfsuid_entry" },
   { 44, "p_sys_setfsuid_ret" },
   { 45, "p_sys_setgid_entry" },
   { 46, "p_sys_setgid_ret" },
   { 47, "p_sys_setns_entry" },
   { 48, "p_sys_setns_ret" },
   { 49, "p_sys_setregid_entry" },
   { 50, "p_sys_setregid_ret" },
   { 51, "p_sys_setresgid_entry" },
   { 52, "p_sys_setresgid_ret" },
   { 53, "p_sys_setresuid_entry" },
   { 54, "p_sys_setresuid_ret" },
   { 55, "p_sys_setreuid_entry" },
   { 56, "p_sys_setreuid_ret" },
   { 57, "p_sys_setuid_entry" },
   { 58, "p_sys_setuid_ret" },
   { 59, "p_sys_unshare_entry" },
   { 60, "p_sys_unshare_ret" },
   { 61, "p_wake_up_new_task_entry" },
   { 0, NULL }

};

static const struct {

   char p_id;
   const char *p_name;

} p_debug_off_flag_action[] = {

   { 0, "OFF" },
   { 1, "ON" },
   { 2, "RESET" },
   { 3, "OVERRIDE OFF" },
   { 4, "OVERRIDE ON" },

};

void p_debug_off_flag_off(struct p_ed_process *p_source, unsigned int p_id) {

   p_source->p_ed_task.p_off_debug_cnt++;

   if (p_source->p_ed_task.p_off_counter < P_LKRG_TASK_OFF_MAXBUF-1) {
      /* Report current event */
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_caller = p_id;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_action = 0;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_old_off =
                                                p_source->p_ed_task.p_off ^ p_global_off_cookie;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_debug_val =
                                                            p_source->p_ed_task.p_off_debug_cnt;
      /* Increment ring-buffer pointer */
      p_source->p_ed_task.p_off_counter++;
   } else {
      /* Mark that we are starting overriding ring-buffer */
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_caller = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_action = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_old_off = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_debug_val = -1;
      /* Reset */
      p_source->p_ed_task.p_off_counter = 0;
      /* Report current event */
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_caller = p_id;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_action = 0;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_old_off =
                                                p_source->p_ed_task.p_off ^ p_global_off_cookie;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_debug_val =
                                                            p_source->p_ed_task.p_off_debug_cnt;
      /* Increment ring-buffer pointer */
      p_source->p_ed_task.p_off_counter++;
   }
}

void p_debug_off_flag_on(struct p_ed_process *p_source, unsigned int p_id) {

   p_source->p_ed_task.p_off_debug_cnt--;

   if (p_source->p_ed_task.p_off_counter < P_LKRG_TASK_OFF_MAXBUF-1) {
      /* Report current event */
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_caller = p_id;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_action = 1;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_old_off =
                                                p_source->p_ed_task.p_off ^ p_global_off_cookie;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_debug_val =
                                                            p_source->p_ed_task.p_off_debug_cnt;
      /* Increment ring-buffer pointer */
      p_source->p_ed_task.p_off_counter++;
   } else {
      /* Mark that we are starting overriding ring-buffer */
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_caller = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_action = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_old_off = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_debug_val =
                                                            p_source->p_ed_task.p_off_debug_cnt;
      /* Reset */
      p_source->p_ed_task.p_off_counter = 0;
      /* Report current event */
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_caller = p_id;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_action = 1;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_old_off =
                                                p_source->p_ed_task.p_off ^ p_global_off_cookie;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_debug_val =
                                                            p_source->p_ed_task.p_off_debug_cnt;
      /* Increment ring-buffer pointer */
      p_source->p_ed_task.p_off_counter++;
   }
}

void p_debug_off_flag_override_off(struct p_ed_process *p_source, unsigned int p_id, struct pt_regs *p_regs) {

   p_source->p_ed_task.p_off_debug_cnt++;

   if (p_source->p_ed_task.p_off_counter < P_LKRG_TASK_OFF_MAXBUF-1) {
      /* Report current event */
      /* Stack trace*/
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.entries =
             (unsigned long *)p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_internal_buf;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.max_entries =
          P_PCFI_STACK_BUF/sizeof(p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.entries[0]);
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.nr_entries = 0;
#if defined(CONFIG_ARCH_STACKWALK) || LINUX_VERSION_CODE >= KERNEL_VERSION(5,2,0)
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.nr_entries =
          stack_trace_save(p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.entries,
                           p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.max_entries,
                           1);
#else
      save_stack_trace(&p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace);
#endif
      /* End */
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_caller = p_id;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_action = 3;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_old_off =
                                                p_source->p_ed_task.p_off ^ p_global_off_cookie;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_debug_val =
                                                            p_source->p_ed_task.p_off_debug_cnt;
      /* Increment ring-buffer pointer */
      p_source->p_ed_task.p_off_counter++;
   } else {
      /* Mark that we are starting overriding ring-buffer */
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_caller = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_action = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_old_off = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_debug_val = -1;
      /* Reset */
      p_source->p_ed_task.p_off_counter = 0;
      /* Report current event */
      /* Stack trace*/
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.entries =
             (unsigned long *)p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_internal_buf;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.max_entries =
          P_PCFI_STACK_BUF/sizeof(p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.entries[0]);
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.nr_entries = 0;
#if defined(CONFIG_ARCH_STACKWALK) || LINUX_VERSION_CODE >= KERNEL_VERSION(5,2,0)
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.nr_entries =
          stack_trace_save(p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.entries,
                           p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.max_entries,
                           1);
#else
      save_stack_trace(&p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace);
#endif
      /* End */
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_caller = p_id;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_action = 3;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_old_off =
                                                p_source->p_ed_task.p_off ^ p_global_off_cookie;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_debug_val =
                                                            p_source->p_ed_task.p_off_debug_cnt;
      /* Increment ring-buffer pointer */
      p_source->p_ed_task.p_off_counter++;
   }
}

void p_debug_off_flag_override_on(struct p_ed_process *p_source, unsigned int p_id, struct pt_regs *p_regs) {

   p_source->p_ed_task.p_off_debug_cnt--;

   if (p_source->p_ed_task.p_off_counter < P_LKRG_TASK_OFF_MAXBUF-1) {
      /* Report current event */
      /* Stack trace*/
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.entries =
             (unsigned long *)p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_internal_buf;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.max_entries =
          P_PCFI_STACK_BUF/sizeof(p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.entries[0]);
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.nr_entries = 0;
#if defined(CONFIG_ARCH_STACKWALK) || LINUX_VERSION_CODE >= KERNEL_VERSION(5,2,0)
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.nr_entries =
          stack_trace_save(p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.entries,
                           p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.max_entries,
                           1);
#else
      save_stack_trace(&p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace);
#endif
      /* End */
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_caller = p_id;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_action = 4;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_old_off =
                                                p_source->p_ed_task.p_off ^ p_global_off_cookie;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_debug_val =
                                                            p_source->p_ed_task.p_off_debug_cnt;
      /* Increment ring-buffer pointer */
      p_source->p_ed_task.p_off_counter++;
   } else {
      /* Mark that we are starting overriding ring-buffer */
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_caller = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_action = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_old_off = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_debug_val = -1;
      /* Reset */
      p_source->p_ed_task.p_off_counter = 0;
      /* Report current event */
      /* Stack trace*/
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.entries =
             (unsigned long *)p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_internal_buf;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.max_entries =
          P_PCFI_STACK_BUF/sizeof(p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.entries[0]);
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.nr_entries = 0;
#if defined(CONFIG_ARCH_STACKWALK) || LINUX_VERSION_CODE >= KERNEL_VERSION(5,2,0)
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.nr_entries =
          stack_trace_save(p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.entries,
                           p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace.max_entries,
                           1);
#else
      save_stack_trace(&p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_trace);
#endif
      /* End */
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_caller = p_id;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_action = 4;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_old_off =
                                                p_source->p_ed_task.p_off ^ p_global_off_cookie;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_debug_val =
                                                            p_source->p_ed_task.p_off_debug_cnt;
      /* Increment ring-buffer pointer */
      p_source->p_ed_task.p_off_counter++;
   }
}

void p_debug_off_flag_reset(struct p_ed_process *p_source, unsigned int p_id) {

   p_source->p_ed_task.p_off_debug_cnt = 0;

   if (p_source->p_ed_task.p_off_counter < P_LKRG_TASK_OFF_MAXBUF-1) {
      /* Report current event */
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_caller = p_id;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_action = 2;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_old_off =
                                                p_source->p_ed_task.p_off ^ p_global_off_cookie;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_debug_val =
                                                            p_source->p_ed_task.p_off_debug_cnt;
      /* Increment ring-buffer pointer */
      p_source->p_ed_task.p_off_counter++;
   } else {
      /* Mark that we are starting overriding ring-buffer */
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_caller = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_action = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_old_off = -1;
      p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_debug_val = -1;
      /* Reset */
      p_source->p_ed_task.p_off_counter = 0;
      /* Report current event */
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_caller = p_id;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_action = 2;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_old_off =
                                                p_source->p_ed_task.p_off ^ p_global_off_cookie;
      p_source->p_ed_task.p_off_debug[p_source->p_ed_task.p_off_counter].p_debug_val =
                                                            p_source->p_ed_task.p_off_debug_cnt;
      /* Increment ring-buffer pointer */
      p_source->p_ed_task.p_off_counter++;
   }
}

static void p_debug_off_flag_dump_ring_buffer(struct p_ed_process *p_source) {

   unsigned int p_tmp;

   p_print_log(P_LOG_WATCH, "OFF debug: normalization[0x%lx] cookie[0x%lx]",
               p_global_cnt_cookie,
               p_global_off_cookie);
   p_print_log(P_LOG_WATCH, "Process[%d | %s] Parent[%d | %s] has TSYNC[%d] and [%d] entries:",
               p_source->p_ed_task.p_pid,
               p_source->p_ed_task.p_comm,
               p_source->p_ed_task.p_task->real_parent->pid,
               p_source->p_ed_task.p_task->real_parent->comm,
               p_source->p_ed_task.p_sec.flag_sync_thread,
               p_source->p_ed_task.p_off_counter);

   if (p_source->p_ed_task.p_off_counter < 3 &&
       p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_caller == -1 &&
       p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_action == -1 &&
       p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_old_off == -1 &&
       p_source->p_ed_task.p_off_debug[P_LKRG_TASK_OFF_MAXBUF-1].p_debug_val == -1) {
      /* OK, ring buffer was overwritten. Dump a few entries before overwrite: */
      p_print_log(P_LOG_WATCH, "Before overwrite:");
      for (p_tmp = P_LKRG_TASK_OFF_MAXBUF-1-6; p_tmp < P_LKRG_TASK_OFF_MAXBUF-1; p_tmp++) {
         p_print_log(P_LOG_WATCH, " => caller[%s] action[%s] old_off[0x%lx] debug_val[%d]",
                     p_debug_off_flag_callers[p_source->p_ed_task.p_off_debug[p_tmp].p_caller].p_name,
                     p_debug_off_flag_action[p_source->p_ed_task.p_off_debug[p_tmp].p_action].p_name,
                     p_source->p_ed_task.p_off_debug[p_tmp].p_old_off,
                     p_source->p_ed_task.p_off_debug[p_tmp].p_debug_val);
         if (p_source->p_ed_task.p_off_debug[p_tmp].p_action == 3 ||
             p_source->p_ed_task.p_off_debug[p_tmp].p_action == 4) {
            p_print_log(P_LOG_WATCH, "Stack trace:");
#if defined(CONFIG_ARCH_STACKWALK) || LINUX_VERSION_CODE >= KERNEL_VERSION(5,2,0)
            stack_trace_print(p_source->p_ed_task.p_off_debug[p_tmp].p_trace.entries,
                              p_source->p_ed_task.p_off_debug[p_tmp].p_trace.nr_entries,
                              0);
#else
            print_stack_trace(&p_source->p_ed_task.p_off_debug[p_tmp].p_trace, 0);
#endif
         }
      }
      p_print_log(P_LOG_WATCH, "=== RING BUFFER OVERRIDE ===");
   }

   for (p_tmp = 0; p_tmp < p_source->p_ed_task.p_off_counter; p_tmp++) {
      p_print_log(P_LOG_WATCH, " => caller[%s] action[%s] old_off[0x%lx] debug_val[%d]",
                  p_debug_off_flag_callers[p_source->p_ed_task.p_off_debug[p_tmp].p_caller].p_name,
                  p_debug_off_flag_action[p_source->p_ed_task.p_off_debug[p_tmp].p_action].p_name,
                  p_source->p_ed_task.p_off_debug[p_tmp].p_old_off,
                  p_source->p_ed_task.p_off_debug[p_tmp].p_debug_val);
      if (p_source->p_ed_task.p_off_debug[p_tmp].p_action == 3 ||
          p_source->p_ed_task.p_off_debug[p_tmp].p_action == 4) {
         p_print_log(P_LOG_WATCH, "Stack trace:");
#if defined(CONFIG_ARCH_STACKWALK) || LINUX_VERSION_CODE >= KERNEL_VERSION(5,2,0)
         stack_trace_print(p_source->p_ed_task.p_off_debug[p_tmp].p_trace.entries,
                           p_source->p_ed_task.p_off_debug[p_tmp].p_trace.nr_entries,
                           0);
#else
         print_stack_trace(&p_source->p_ed_task.p_off_debug[p_tmp].p_trace, 0);
#endif
      }
   }
}
#endif

static inline int p_off_depth(long *p_val) {

   int p_depth = 0;

   while (*p_val > p_global_cnt_cookie) {
      *p_val -= p_global_cnt_cookie;
      p_depth++;
      if (unlikely(*p_val > (p_global_cnt_cookie << 3)))
         break;
   }

   return p_depth;
}

static inline void p_ed_is_off_off(struct p_ed_process *p_source, long p_val, int *p_ret) {

   if (unlikely(p_val != p_global_cnt_cookie)) {
      long p_val_remainder = p_val;
      int p_depth = p_off_depth(&p_val_remainder);
      if (p_val_remainder != p_global_cnt_cookie)
         p_depth = p_val ? 0 : -1;
      /* Depths -1 or 1+ are exact, 0 means non-multiple or out of range */
      p_print_log(P_LOG_ALERT, "DETECT: Task: unexpected 'off' flag depth %d for pid %u, name %s",
         p_depth, p_source->p_ed_task.p_pid, p_source->p_ed_task.p_comm);
#ifdef P_LKRG_TASK_OFF_DEBUG
      p_print_log(P_LOG_WATCH, "'off' flag[0x%lx] (normalization via 0x%lx)",
         p_val, p_global_cnt_cookie);
      p_debug_off_flag_dump_ring_buffer(p_source);
      dump_stack();
#else
      p_debug_log(P_LOG_DEBUG, "'off' flag[0x%lx] (normalization via 0x%lx)",
         p_val, p_global_cnt_cookie);
#endif
      // kill this process!
      if (p_ret)
         (*p_ret)++;
      else
         p_ed_kill_task(p_source);
   }
}

static inline void p_validate_off_flag(struct p_ed_process *p_source, long p_val, int *p_ret) {

   if (likely(p_val == p_global_cnt_cookie))
      return;

   p_off_depth(&p_val);

   p_ed_is_off_off(p_source, p_val, p_ret);
}

#if P_OVL_OVERRIDE_SYNC_MODE
int p_verify_ovl_override_sync(struct p_ed_process *p_source) {

   unsigned long p_off = p_source->p_ed_task.p_off ^ p_global_off_cookie; // Decode

   p_validate_off_flag(p_source,p_off,NULL);   // Validate
#if P_OVL_OVERRIDE_SYNC_MODE == 2
   return p_off == 3 * p_global_cnt_cookie;
#else
   return p_off == 2 * p_global_cnt_cookie;
#endif
}
#endif

void p_ed_is_off_off_wrap(struct p_ed_process *p_source) {

   unsigned long p_off = p_source->p_ed_task.p_off ^ p_global_off_cookie; // Decode
   p_ed_is_off_off(p_source,p_off,NULL);
}

void p_ed_validate_off_flag_wrap(struct p_ed_process *p_source) {

   unsigned long p_off = p_source->p_ed_task.p_off ^ p_global_off_cookie; // Decode
   p_validate_off_flag(p_source,p_off,NULL);   // Validate
}

static inline void p_cmp_cred_ptr(struct p_ed_process *edp);

void p_set_ed_process_on(struct p_ed_process *p_source) {

   unsigned long p_off = p_source->p_ed_task.p_off ^ p_global_off_cookie; // Decode

#if defined(CONFIG_SECCOMP)
   if (p_source->p_ed_task.p_sec.flag_sync_thread) {
      p_set_ed_process_override_on(p_source);
   } else {
#endif
      p_off -= p_global_cnt_cookie;               // Normalize
      p_ed_is_off_off(p_source,p_off,NULL);       // Validate

      p_source->p_ed_task.p_off = p_off ^ p_global_off_cookie; // Encode
      p_source->p_ed_task.p_off_count = 0;
#if defined(CONFIG_SECCOMP)
   }
#endif

   p_cmp_cred_ptr(p_source);
}

void p_set_ed_process_off(struct p_ed_process *p_source) {

   unsigned long p_off = p_source->p_ed_task.p_off ^ p_global_off_cookie; // Decode

#if defined(CONFIG_SECCOMP)
   if (p_source->p_ed_task.p_sec.flag_sync_thread) {
      p_set_ed_process_override_off(p_source);
   } else {
#endif
      p_ed_is_off_off(p_source,p_off,NULL);       // Validate
      p_off += p_global_cnt_cookie;               // Normalize

      p_source->p_ed_task.p_off = p_off ^ p_global_off_cookie;
#if defined(CONFIG_SECCOMP)
   }
#endif

   p_cmp_cred_ptr(p_source);
}

void p_set_ed_process_override_on(struct p_ed_process *p_source) {

   unsigned long p_off = p_source->p_ed_task.p_off ^ p_global_off_cookie; // Decode

   p_validate_off_flag(p_source,p_off,NULL);   // Validate
   p_off -= p_global_cnt_cookie;               // Normalize

   p_source->p_ed_task.p_off = p_off ^ p_global_off_cookie; // Encode
   if (p_off == p_global_cnt_cookie)
      p_source->p_ed_task.p_off_count = 0;
}

void p_set_ed_process_override_off(struct p_ed_process *p_source) {

   unsigned long p_off = p_source->p_ed_task.p_off ^ p_global_off_cookie; // Decode

   p_validate_off_flag(p_source,p_off,NULL);   // Validate
   p_off += p_global_cnt_cookie;               // Normalize

   p_source->p_ed_task.p_off = p_off ^ p_global_off_cookie;
}

static int p_insert_ed_task(struct p_ed_process *p_source) {

   int ret;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)
   struct p_ed_process *p_tmp;
   struct task_struct *thread;
   unsigned long flags;

   /*
    * Find any other task in the thread group to synchronize the seccomp
    * SECCOMP_FILTER_FLAG_TSYNC depth (flag_sync_thread). All tasks in the
    * thread group are guaranteed to have the same depth, and since the depth is
    * only ever changed under the sighand lock, there's no need to lock the ed
    * struct to read another task's flag_sync_thread here.
    *
    * To prevent a race where another task starting a seccomp with
    * SECCOMP_FILTER_FLAG_TSYNC may miss this task because it's not in the ed
    * tree yet, additions to the ed tree must be done under the sighand lock.
    */
   spin_lock_irqsave(&current->sighand->siglock, flags);
   /*
    * An RCU read lock is needed for all dereferences into another task's ed
    * struct in order to prevent it from getting freed while it's used, as the
    * sighand lock alone doesn't keep ed structs pinned in memory. Just take the
    * RCU read lock once for the entire loop for simplicity.
    */
   rcu_read_lock();
   for_each_thread(current, thread) {
      if (thread == current)
         continue;

      /* FYI, this DOESN'T lock the ed struct! It's not needed (see above). */
      p_tmp = __ed_task_find_rcu(thread);
      if (p_tmp) {
         /*
          * Copy flag_sync_thread and set the off flag if there's at least one
          * SECCOMP_FILTER_FLAG_TSYNC in flight within the thread group.
          */
         if ((p_source->p_ed_task.p_sec.flag_sync_thread = p_tmp->p_ed_task.p_sec.flag_sync_thread))
            p_set_ed_process_off(p_source);
         break;
      }
   }
   rcu_read_unlock();
#endif
   ret = ed_task_add(p_source);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)
   spin_unlock_irqrestore(&current->sighand->siglock, flags);
#endif
   return ret;
}

void p_dump_task_f(void *p_arg) {

   struct task_struct *p_task = (struct task_struct *)p_arg;
   struct p_ed_process *p_tmp;

   if (unlikely((p_tmp = alloc_ed_task(p_task)) == NULL)) {
      p_print_log(P_LOG_FAULT, "Can't allocate memory for tracking pid %u, name %s", p_task->pid, p_task->comm);
      return;
   }

   p_update_ed_process(p_tmp, 1);
//   p_set_ed_process_on(p_tmp);
   p_tmp->p_ed_task.p_off = p_global_cnt_cookie ^ p_global_off_cookie;
   p_tmp->p_ed_task.p_off_count = 0;
#ifdef P_LKRG_TASK_OFF_DEBUG
   p_tmp->p_ed_task.p_off_debug_cnt = p_tmp->p_ed_task.p_off_counter = 0;
#endif

   if (unlikely(p_insert_ed_task(p_tmp))) {
      struct p_ed_process *p_have;
      rcu_read_lock();
      if ((p_have = ed_task_find_lock_rcu(p_task))) {
         p_print_log(P_LOG_FAULT, "Duplicate key for tracking pid %u, name %s (have pid %u, name %s)",
                     p_tmp->p_ed_task.p_pid, p_tmp->p_ed_task.p_comm,
                     p_have->p_ed_task.p_pid, p_have->p_ed_task.p_comm);
         ed_task_unlock(p_have);
      } else {
         p_print_log(P_LOG_FAULT, "Duplicate key for tracking pid %u, name %s",
                     p_tmp->p_ed_task.p_pid, p_tmp->p_ed_task.p_comm);
      }
      rcu_read_unlock();
      free_ed_task(p_tmp);
   } else {
      p_print_log(P_LOG_WATCH, "Inserting pid %u, name %s",
                  p_tmp->p_ed_task.p_pid, p_tmp->p_ed_task.p_comm);
   }
}

static void p_dump_ed_tasks(void) {

   struct task_struct *p_ptmp, *p_tmp;

   rcu_read_lock();
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
   for_each_process_thread(p_ptmp, p_tmp) {
#else
   // tasklist_lock
   do_each_thread(p_ptmp, p_tmp) {
#endif

      /* do not touch kernel threads or the global init */
      if (p_is_ed_task(p_tmp))
         p_dump_task_f(p_tmp);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
   }
#else
   // tasklist_unlock
   } while_each_thread(p_ptmp, p_tmp);
#endif
   rcu_read_unlock();
}

static unsigned int p_iterate_lkrg_tasks_paranoid(void) {

   int p_ret = 0;
   struct p_ed_process *p_tmp;
   struct task_struct *p_ptmp, *p_task;

   rcu_read_lock();
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
   for_each_process_thread(p_ptmp, p_task) {
#else
   do_each_thread(p_ptmp, p_task) {
#endif

      /* do not touch kernel threads or the global init */
      if (p_is_ed_task(p_task) && p_get_task_state(p_task) != TASK_DEAD) {
         if ((p_tmp = ed_task_find_lock_rcu(p_task))) {
            if (unlikely(p_cmp_tasks(p_tmp, 0))) {
               p_ret++;
               p_ed_kill_task(p_tmp);
            }
            ed_task_unlock(p_tmp);
         }
      }

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
   }
#else
   } while_each_thread(p_ptmp, p_task);
#endif
   rcu_read_unlock();

   /* Before leaving, verify current task */
   p_ed_find_validate_current();

   return p_ret;
}

static int p_cmp_creds(struct p_cred *p_orig, const struct cred *p_current_cred, struct task_struct *p_current) {

   int p_ret = 0;

#define P_CMP_CRED(eq, get, cred) \
   if (unlikely(!eq(p_orig->cred, p_current_cred->cred))) { \
      p_print_log(P_LOG_ALERT, \
         "DETECT: Task: " #cred " corruption (expected %u vs. actual %u) for pid %u, name %s", \
         get(&p_orig->cred), get(&p_current_cred->cred), \
         task_pid_nr(p_current), p_current->comm); \
      p_ret++; \
   }

   /* *UID */
   P_CMP_CRED(uid_eq, p_get_uid, uid)
   P_CMP_CRED(uid_eq, p_get_uid, euid)
   P_CMP_CRED(uid_eq, p_get_uid, suid)
   P_CMP_CRED(uid_eq, p_get_uid, fsuid)

   /* *GID */
   P_CMP_CRED(gid_eq, p_get_gid, gid)
   P_CMP_CRED(gid_eq, p_get_gid, egid)
   P_CMP_CRED(gid_eq, p_get_gid, sgid)
   P_CMP_CRED(gid_eq, p_get_gid, fsgid)

#define P_CMP_PTR(orig, curr, name) \
   if (unlikely(orig != curr)) { \
      if (P_CTRL(p_log_level) >= P_LOG_WATCH) \
         p_print_log(P_LOG_ALERT, \
            "DETECT: Task: " name " pointer corruption (expected 0x%lx vs. actual 0x%lx) for pid %u, name %s", \
            (unsigned long)orig, (unsigned long)curr, task_pid_nr(p_current), p_current->comm); \
      else \
         p_print_log(P_LOG_ALERT, \
            "DETECT: Task: " name " pointer corruption for pid %u, name %s", \
            task_pid_nr(p_current), p_current->comm); \
      p_ret++; \
   }

   /* Namespaces */
#define P_NS_ESCAPE "Namespace escape: "
   P_CMP_PTR(p_orig->user_ns, p_current_cred->user_ns, P_NS_ESCAPE "user_ns")

   return p_ret;
}

static int p_cmp_tasks(struct p_ed_process *p_orig, char p_kill) {

   struct task_struct *p_current = p_orig->p_ed_task.p_task;
   int p_ret = 0, p_killed = 0;
   long p_off = p_orig->p_ed_task.p_off ^ p_global_off_cookie;
   const struct cred *p_current_cred = NULL;
   const struct cred *p_current_real_cred = NULL;
   int p_seccomp_mode;

   if (p_off - p_global_cnt_cookie) {
      if (p_kill)
         p_validate_off_flag(p_orig,p_off,NULL);     // Validate
      else
         p_validate_off_flag(p_orig,p_off,&p_ret);   // Validate

      p_orig->p_ed_task.p_off_count++;

      if (unlikely(p_orig->p_ed_task.p_off_count > P_ED_PROCESS_OFF_MAX)) {
         /* That's weird and it might be a potentially compromised process */
         p_print_log(P_LOG_WATCH, "Validation was off %u times in a row for pid %u, name %s",
            p_orig->p_ed_task.p_off_count, p_orig->p_ed_task.p_pid, p_orig->p_ed_task.p_comm);
      }
      return 0;
   }

   /* Validate stack first */
   if (unlikely(p_ed_pcfi_validate_sp(NULL, p_orig, p_get_thread_sp(p_current)))) {
      if (p_kill) {
         p_pcfi_kill_ed_task(p_orig);
         p_killed = 1;
      }
      p_ret++;
   }

   /*
    * Fetch pointers first
    */
   rcu_read_lock();
   p_current_cred = rcu_dereference(p_current->cred);
   p_current_real_cred = rcu_dereference(p_current->real_cred);

   /*
    * Valid cases here:
    * 1. No credentials change nor override, so both data and pointers match.
    * 2. Untracked commit_creds(), but no change in tracked credentials, so
    *    data matches, but pointers differ from saved, yet match each other.
    *    However, we've just fetched them non-atomically, so this condition is
    *    only valid for the current task.
    * 3. Untracked override_creds() on 6.13+, so real_cred data and pointer
    *    match what we saved, but cred differs from real_cred.
    *
    * Invalid cases here:
    * 1. Untracked commit_creds() that changes our tracked credentials should
    *    not exist - we're supposed to track all callers of such.
    * 2. Untracked override_creds() on pre-6.13 should not exist - we track
    *    override_creds() itself.
    * 3. Tracked commit_creds() that's still in progress should set "off" for
    *    the task, which we check for above, so would not reach here.
    * 4. Tracked maybe-nested override_creds() on pre-6.13 should set "off" for
    *    the task until a final/outer revert_creds(), so we would not reach
    *    here with override still in effect.
    */

   /*
    * Check the actual credentials and report any differences.  This directly
    * checks the objective credentials only, not the subjective maybe-override
    * credentials, but indirectly also the latter when the pointers are the
    * same (that is, when no override nor a pointer hack is in progress).
    */
   p_ret += p_cmp_creds(&p_orig->p_ed_task.p_real_cred, p_current_real_cred, p_current);

   /*
    * For the current task, the two pointers we fetched must be consistent with
    * each other, which lets us perform additional checks.
    *
    * commit_creds() changes both pointers, override_creds() only the cred
    * pointer, but nothing valid changes only the real_cred pointer.
    * Unfortunately, we cannot reliably detect this as a violation on pre-6.13
    * kernels because there may have been both an untracked commit_creds()
    * changing both pointers and an override_creds() changing the cred pointer
    * potentially in such a way that the old pointer value we saved is reused.
    *
    * On kernels below 6.13, we directly detect pointers differing from each
    * other as a violation, which covers the above case and also unexpected
    * override_creds() and possible different corruption of both pointers.
    *
    * Since in all of these cases we know that the two current pointers differ
    * from each other, a check of the actual subjective credentials wouldn't be
    * redundant with what we performed above, so we perform it as well.
    */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,13,0)
   if (current == p_current && unlikely(p_current_real_cred != p_current_cred)) {
      P_CMP_PTR(p_orig->p_ed_task.p_real_cred_ptr, p_current_real_cred, "real_cred")
      p_ret += p_cmp_creds(&p_orig->p_ed_task.p_real_cred, p_current_cred, p_current);
      P_CMP_PTR(p_orig->p_ed_task.p_real_cred_ptr, p_current_cred, "cred")
   }
#endif

   /* Namespaces */
   if (p_orig->p_ed_task.p_nsproxy && (p_current == current || spin_trylock(&p_current->alloc_lock))) {
      if (p_current->nsproxy) {
         P_CMP_PTR(p_orig->p_ed_task.p_nsproxy, p_current->nsproxy, P_NS_ESCAPE "nsproxy")
         P_CMP_PTR(p_orig->p_ed_task.p_ns.uts_ns, p_current->nsproxy->uts_ns, P_NS_ESCAPE "uts_ns")
         P_CMP_PTR(p_orig->p_ed_task.p_ns.ipc_ns, p_current->nsproxy->ipc_ns, P_NS_ESCAPE "ipc_ns")
         P_CMP_PTR(p_orig->p_ed_task.p_ns.mnt_ns, p_current->nsproxy->mnt_ns, P_NS_ESCAPE "mnt_ns")
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,11,0)
         P_CMP_PTR(p_orig->p_ed_task.p_ns.pid_ns_for_children, p_current->nsproxy->pid_ns_for_children, P_NS_ESCAPE "pid_ns_for_children")
#else
         P_CMP_PTR(p_orig->p_ed_task.p_ns.pid_ns, p_current->nsproxy->pid_ns, P_NS_ESCAPE "pid_ns")
#endif
         P_CMP_PTR(p_orig->p_ed_task.p_ns.net_ns, p_current->nsproxy->net_ns, P_NS_ESCAPE "net_ns")
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0)
         P_CMP_PTR(p_orig->p_ed_task.p_ns.cgroup_ns, p_current->nsproxy->cgroup_ns, P_NS_ESCAPE "cgroup_ns")
#endif
      }
      if (p_current != current)
         spin_unlock(&p_current->alloc_lock);
   }

#if defined(CONFIG_SECCOMP)
   /* Seccomp */
   if (p_orig->p_ed_task.p_sec.flag) { // SECCOMP was enabled so it make sense to compare...

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,11,0)
      if (unlikely(test_task_syscall_work(p_current,SECCOMP) != p_orig->p_ed_task.p_sec.flag)) {
#else
      if (unlikely(test_tsk_thread_flag(p_current,TIF_SECCOMP) != p_orig->p_ed_task.p_sec.flag)) {
#endif
         p_print_log(P_LOG_ALERT, "DETECT: Task: TIF_SECCOMP flag corruption (expected %u vs. actual %u) for pid %u, name %s",
            p_orig->p_ed_task.p_sec.flag,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,11,0)
            test_task_syscall_work(p_current, SECCOMP),
#else
            test_tsk_thread_flag(p_current, TIF_SECCOMP),
#endif
            task_pid_nr(p_current), p_current->comm);
         p_ret++;
      }

      /*
       * Use READ_ONCE because SECCOMP_MODE_DEAD may be set async to our checks
       * when we are validating other than the current task, yet we need our
       * checks to be internally consistent.  Limit SECCOMP_MODE_DEAD support
       * to kernels that actually have it, so that this magic value cannot be
       * used to silently bypass our validation on older kernels.  The kernels
       * that support it will kill the SECCOMP_MODE_DEAD task on their own.
       */
      p_seccomp_mode = READ_ONCE(p_current->seccomp.mode);
      if (unlikely(p_orig->p_ed_task.p_sec.sec.mode != p_seccomp_mode)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,16,10) || \
    (LINUX_VERSION_CODE < KERNEL_VERSION(5,16,0) && LINUX_VERSION_CODE >= KERNEL_VERSION(5,15,24)) || \
    (LINUX_VERSION_CODE < KERNEL_VERSION(5,11,0) && LINUX_VERSION_CODE >= KERNEL_VERSION(5,10,211)) || \
    (LINUX_VERSION_CODE < KERNEL_VERSION(5,5,0) && LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,180))
          && p_seccomp_mode != 3 /* SECCOMP_MODE_DEAD on 5.17+ and backports */
#endif
          ) {
         if (p_seccomp_mode < 0 || p_seccomp_mode > 2
             || p_orig->p_ed_task.p_sec.sec.mode < 0 || p_orig->p_ed_task.p_sec.sec.mode > 2) {
            p_print_log(P_LOG_ALERT, "DETECT: Task: seccomp mode corruption (expected %u vs. actual %u) for pid %u, name %s",
               p_orig->p_ed_task.p_sec.sec.mode, p_seccomp_mode, task_pid_nr(p_current), p_current->comm);
         } else {
            static const char * const p_sec_strings[] = {"SECCOMP_MODE_DISABLED", "SECCOMP_MODE_STRICT", "SECCOMP_MODE_FILTER"};
            p_print_log(P_LOG_ALERT, "DETECT: Task: seccomp mode corruption (expected %s vs. actual %s) for pid %u, name %s",
               p_sec_strings[p_orig->p_ed_task.p_sec.sec.mode], p_sec_strings[p_seccomp_mode],
               task_pid_nr(p_current), p_current->comm);
         }
         p_ret++;
      }

      if (current == p_current && !(p_current->flags & PF_EXITING))
         P_CMP_PTR(p_orig->p_ed_task.p_sec.sec.filter, p_current->seccomp.filter, "seccomp filter")
   }
#endif

   rcu_read_unlock();

   return p_killed ? -p_ret : p_ret;
}

/*
 * Compensate for the relaxed cred pointer checks in p_cmp_tasks() especially
 * on 6.13+ by doing strict checks where no creds override is expected/allowed.
 */
static inline void p_cmp_cred_ptr(struct p_ed_process *edp) {

   struct task_struct *p_current = edp->p_ed_task.p_task;
   const struct cred *p_current_cred = p_current->cred;
   const struct cred *p_current_real_cred = p_current->real_cred;
   int p_ret = 0;

   P_CMP_PTR(p_current_real_cred, p_current_cred, "cred or/and real_cred")

   if (unlikely(p_ret))
      p_ed_kill_task(edp);
}

static void p_ed_validate_kill(struct p_ed_process *p_source) {

   if (unlikely(p_cmp_tasks(p_source, 1) > 0)) {
      // kill this process!
      p_ed_kill_task(p_source);
   }
}

void p_ed_find_validate_current(void) {

   struct p_ed_process *p_tmp;

   if (!p_is_ed_task(current))
      return;

   if ((p_tmp = ed_task_lock_current())) {
      p_ed_validate_kill(p_tmp);
      ed_task_unlock(p_tmp);
   }
}

#ifdef CONFIG_SECURITY_SELINUX
static void p_validate_selinux(void) {
   do {
      p_lkrg_counter_lock_lock(&p_ed_guard_globals.p_selinux_lock);
      if (!p_lkrg_counter_lock_val_read(&p_ed_guard_globals.p_selinux_lock))
         break;
      p_lkrg_counter_lock_unlock(&p_ed_guard_globals.p_selinux_lock);
      schedule();
   } while(1);

#if (!defined(RHEL_RELEASE_CODE) && LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0)) || \
     (defined(RHEL_RELEASE_CODE) && RHEL_RELEASE_CODE < RHEL_RELEASE_VERSION(8, 3))
   if (p_ed_guard_globals.p_selinux.p_selinux_enabled != *P_SYM(p_selinux_enabled)) {
      p_print_log(P_LOG_ALERT, "BLOCK: SELinux: selinux_enabled corruption (expected %u vs. actual %u), restoring",
         p_ed_guard_globals.p_selinux.p_selinux_enabled, *P_SYM(p_selinux_enabled));
/* FIXME: should obey kint_enforce here */
      *P_SYM(p_selinux_enabled) = p_ed_guard_globals.p_selinux.p_selinux_enabled;
   }
#endif

#ifdef P_SELINUX_VERIFY
   if (p_selinux_state_changed()) {
      p_print_log(P_LOG_ALERT, "DETECT: SELinux: selinux_state->enforcing corruption (expected %u vs. actual %u)",
         p_ed_guard_globals.p_selinux.p_selinux_enforcing, p_selinux_state_enforcing());

      switch (P_CTRL(p_kint_enforce)) {

         /* Panic */
         case 2:
           // OK, we need to crash the kernel now
           p_panic("SELinux: selinux_state->enforcing corruption (expected %u vs. actual %u)",
              p_ed_guard_globals.p_selinux.p_selinux_enforcing, p_selinux_state_enforcing());
           break;

         case 1:
           p_print_log(P_LOG_ALERT, "BLOCK: SELinux: Restoring selinux_state->enforcing to %u",
              p_ed_guard_globals.p_selinux.p_selinux_enforcing);
           p_selinux_state_restore();
           break;

         case 0:
           p_print_log(P_LOG_ALERT, "ALLOW: SELinux: Accepting new value for selinux_state->enforcing");
           p_selinux_state_update();
           break;

      }

   }
#endif
   p_lkrg_counter_lock_unlock(&p_ed_guard_globals.p_selinux_lock);
}
#endif

static void p_ed_wq_valid_work(struct work_struct *p_work) {

#ifdef CONFIG_SECURITY_SELINUX
   // SELinux
   p_validate_selinux();
#endif

   /* Free the worker struct */
   if (p_work) {
      p_ed_free_valid(p_work);
   }
}

static void p_ed_validate_globals(void) {

   struct work_struct *p_worker;

   if (P_CTRL(p_kint_validate)) {

      /* Validate globals... */
      // ...

      /* Prepare for validation which requires 'sleeping' */
      while ( (p_worker = p_ed_alloc_valid()) == NULL); // Should never be NULL
      INIT_WORK(p_worker, p_ed_wq_valid_work);
      /* schedule for execution */
      queue_work(system_unbound_wq, p_worker);

   }
}

void p_ed_validate_current(struct p_ed_process *p_source) {

   if (!P_CTRL(p_pint_validate))
      return;

   p_ed_validate_kill(p_source);
}

void p_ed_enforce_validation(void) {
   p_ed_pcfi_cpu(0);

   switch (P_CTRL(p_pint_validate)) {

      case 3:
        p_ed_enforce_validation_paranoid();
        break;

      case 2:
      case 1:
        p_ed_find_validate_current();
        break;

      case 0:
        break;
   }

   /* Validate critical globals */
//   p_ed_validate_globals();
}

unsigned int p_ed_enforce_validation_paranoid(void) {

   unsigned int p_ret = 0;

   p_ed_pcfi_cpu(0);

   /* Validate processes and threads */
   if (P_CTRL(p_pint_validate))
      p_ret = p_iterate_lkrg_tasks_paranoid();

   /* Validate critical globals */
   p_ed_validate_globals();

   return p_ret;
}

static void p_ed_pcfi_cache_zero(void *p_arg) {

   unsigned long *p_page = p_arg;

   memset(p_page, 0, P_PCFI_STACK_BUF);
}

static int p_ed_pcfi_cache_init(void) {

   if ( (p_ed_pcfi_cache = kmem_cache_create("p_ed_pcfi_cache", P_PCFI_STACK_BUF,
                                            0, P_LKRG_CACHE_FLAGS, p_ed_pcfi_cache_zero)) == NULL) {
      return P_LKRG_GENERAL_ERROR;
   }

   return P_LKRG_SUCCESS;
}

static void p_ed_pcfi_cache_delete(void) {

   if (p_ed_pcfi_cache) {
      kmem_cache_destroy(p_ed_pcfi_cache);
      p_ed_pcfi_cache = NULL;
   }
}

#if defined(CONFIG_X86_64)
 #define P_KERNEL_LAST_UNUSED_HOLE 0xffffffffffe00000
#elif defined(CONFIG_X86_32)
 #define P_KERNEL_LAST_UNUSED_HOLE 0xfffff000
#elif defined(CONFIG_ARM64)
 #define P_KERNEL_LAST_UNUSED_HOLE 0xffffffe000000000
#elif defined(CONFIG_ARM)
 #define P_KERNEL_LAST_UNUSED_HOLE 0xffff0000
#else
 #define P_KERNEL_LAST_UNUSED_HOLE ((unsigned long)-1 - PAGE_SIZE)
#endif

static inline int p_is_obj_on_stack(struct task_struct *p_task, const void *p_addr, size_t p_size) {

   void *p_stack = p_task->stack; //task_stack_page(p_task);

   return (p_addr >= p_stack) &&
          ((unsigned long)p_addr < (P_KERNEL_LAST_UNUSED_HOLE - p_size)) &&
          (p_addr + p_size < (p_stack + THREAD_SIZE));
}

int p_ed_enforce_pcfi(struct task_struct *p_task, struct p_ed_process *p_orig, struct pt_regs *p_regs) {

   unsigned int i = 0;
   struct stack_trace p_trace;
#ifndef CONFIG_UNWINDER_ORC
   const void *p_fp = (const void *)p_regs_get_fp(p_regs);
#endif
#ifdef CONFIG_X86
#if defined(CONFIG_UNWINDER_ORC)
   struct unwind_state p_state;
#else
   struct stack_frame p_frame;
#endif
#elif defined(CONFIG_ARM64)
#  if LINUX_VERSION_CODE >= KERNEL_VERSION(5,19,0) || \
   (defined(RHEL_RELEASE_CODE) && RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(9,2))
   struct unwind_state p_frame;
#  else
   struct stackframe p_frame;
#  endif
#elif defined(CONFIG_ARM)
   struct stackframe p_frame;
   unsigned long p_high, p_low;
#endif
   unsigned int p_offset = 1;
   char p_sym1[KSYM_SYMBOL_LEN];
   char p_not_valid = 0;
   unsigned long p_sp_addr;

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

   if (in_interrupt())
      p_sp_addr = p_get_thread_sp(p_task);
   else
      p_sp_addr = p_regs_get_sp(p_regs);

   if (p_ed_pcfi_validate_sp(p_task,p_orig,p_sp_addr)) {
#ifndef CONFIG_UNWINDER_ORC
p_ed_enforce_pcfi_do:
#endif
      // kill this process!
      p_pcfi_kill_edp_or_task(p_orig, p_task);
      p_not_valid = 1;
      goto p_ed_enforce_pcfi_out;
   }

   if (P_CTRL(p_pcfi_validate) < 2)
      goto p_ed_enforce_pcfi_out;

#ifndef CONFIG_UNWINDER_ORC
   if (!p_is_obj_on_stack(p_task, p_fp, sizeof(void *))) {
      p_print_log(P_LOG_ALERT, "DETECT: Task: Frame pointer is not on the stack for pid %u, name %s",
         task_pid_nr(p_task), p_task->comm);
      goto p_ed_enforce_pcfi_do;
   }
#endif

   while ( (p_trace.entries = p_ed_pcfi_alloc()) == NULL); // Should never be NULL

   p_trace.max_entries = P_PCFI_STACK_BUF/sizeof(p_trace.entries[0]);
   p_trace.nr_entries = 0;


#if defined(CONFIG_X86)

#if defined(CONFIG_UNWINDER_ORC)

   if (p_trace.nr_entries < p_trace.max_entries)
      p_trace.entries[p_trace.nr_entries++] = p_regs_get_ip(p_regs);

   do {
      unsigned long p_addr;

      for (unwind_start(&p_state, p_task, p_regs, NULL);
           !unwind_done(&p_state) && p_trace.nr_entries < p_trace.max_entries;
           unwind_next_frame(&p_state)) {
         p_addr = unwind_get_return_address(&p_state);
         if (!p_addr)
            break;
         p_trace.entries[p_trace.nr_entries++] = p_addr;
      }
   } while(0);

#else

   if (p_trace.nr_entries < p_trace.max_entries)
      p_trace.entries[p_trace.nr_entries++] = p_regs_get_ip(p_regs);

   while (p_trace.nr_entries < p_trace.max_entries) {
      p_frame.next_frame = NULL;
      p_frame.return_address = 0;

      if ((unsigned long)p_fp < p_regs_get_sp(p_regs) || !p_is_obj_on_stack(p_task, p_fp, sizeof(struct stack_frame)))
         break;

      memcpy(&p_frame, p_fp, sizeof(struct stack_frame));
      if (p_frame.return_address) {
         p_trace.entries[p_trace.nr_entries++] = p_frame.return_address;
      }

      if (p_fp == p_frame.next_frame)
         break;

      p_fp = p_frame.next_frame;
    }

#endif

#elif defined(CONFIG_ARM)

   p_frame.fp = (unsigned long)p_fp;
   p_frame.sp = thread_saved_sp(p_task);
   p_frame.pc = instruction_pointer(p_regs);

   if (current != p_task) {
      if (p_trace.nr_entries < p_trace.max_entries) {
         if (p_frame.pc) {
            p_trace.entries[p_trace.nr_entries++] = p_frame.pc;
         }
      }
   }

   while (p_trace.nr_entries < p_trace.max_entries) {
      p_fp = (void *)p_frame.fp;
      p_low = p_frame.sp;
      p_high = ALIGN(p_low, THREAD_SIZE);

/*
      if ((unsigned long)p_fp < p_regs_get_sp(p_regs) ||
          !p_is_obj_on_stack(current, p_fp) ||
          (unsigned long)(p_fp) & 0xf)
         break;
*/

      if ((unsigned long)p_fp < p_low + 12 || (unsigned long)p_fp > p_high - 4)
         break;

      p_frame.fp = *(unsigned long *)(p_fp - 12);
      p_frame.sp = *(unsigned long *)(p_fp - 8);
      p_frame.pc = *(unsigned long *)(p_fp - 4);

      if (p_frame.pc) {
         p_trace.entries[p_trace.nr_entries++] = p_frame.pc;
      }

      if (!p_frame.fp && !p_frame.pc)
         break;
   }

#elif defined(CONFIG_ARM64)

   p_frame.fp = (unsigned long)p_fp;
   p_frame.pc = p_regs_get_ip(p_regs);

   if (p_trace.nr_entries < p_trace.max_entries) {
      if (p_frame.pc) {
         p_trace.entries[p_trace.nr_entries++] = p_frame.pc;
      }
   }

   while (p_trace.nr_entries < p_trace.max_entries) {
      p_fp = (void *)p_frame.fp;

      if ((unsigned long)p_fp < p_regs_get_sp(p_regs) ||
          !p_is_obj_on_stack(current, p_fp, 8) ||
          (unsigned long)(p_fp) & 0xf)
         break;

      p_frame.fp = *(unsigned long *)(p_fp);
      p_frame.pc = *(unsigned long *)(p_fp + 8);

      if (p_frame.pc) {
         p_trace.entries[p_trace.nr_entries++] = p_frame.pc;
      }

      if (!p_frame.fp && !p_frame.pc)
         break;
   }

#else

   goto p_ed_enforce_pcfi_unlock_out;

#endif

   if (p_trace.nr_entries) {

      for (i = 0; i < p_trace.nr_entries-p_offset; i++) {
         if (!P_SYM_CALL(p___kernel_text_address, p_trace.entries[i])) {
            if (p_trace.nr_entries-p_offset > 4 && i > 4) {
               memset(p_sym1,0,KSYM_SYMBOL_LEN);
               sprint_symbol_no_offset(p_sym1,p_trace.entries[i-1]);
               if (!strncmp(p_sym1, "ret_from_fork", 13)) {
                  memset(p_sym1,0,KSYM_SYMBOL_LEN);
                  sprint_symbol_no_offset(p_sym1,p_trace.entries[i-2]);
                  if (!strncmp(p_sym1, "kthread", 7)) {
                     continue;
                  } else {
                     p_not_valid = 1;
                     break;
                  }
               }
               p_not_valid = 1;
               break;
            } else {
               p_not_valid = 1;
               break;
            }
         }
      }
   }

   if (p_not_valid) {
      p_print_log(P_LOG_ALERT, "DETECT: Task: Invalid stack trace for pid %u, name %s",
         task_pid_nr(p_task), p_task->comm);
      if (P_CTRL(p_log_level) >= P_LOG_WATCH) {
         p_print_log(P_LOG_WATCH, "Invalid frame is %u (0x%lx) out of %u for pid %u, name %s",
            i, p_trace.entries[i], p_trace.nr_entries, task_pid_nr(p_task), p_task->comm);
#ifdef CONFIG_STACKTRACE
/*
 * stack_trace_print() uses unprefixed printk(), which means default severity,
 * so we do the same here to match it regardless of what it is.
 */
         printk("--- STACK TRACE START ---\n");
#if defined(CONFIG_ARCH_STACKWALK) || LINUX_VERSION_CODE >= KERNEL_VERSION(5,2,0)
         stack_trace_print(p_trace.entries, p_trace.nr_entries, 0);
#else
         print_stack_trace(&p_trace, 0);
#endif
         printk("--- STACK TRACE END ---\n");
#endif
      }
      p_pcfi_kill_edp_or_task(p_orig, p_task);
   }

#if !defined(CONFIG_X86) && !defined(CONFIG_ARM64) && !defined(CONFIG_ARM)

p_ed_enforce_pcfi_unlock_out:

#endif

   p_ed_pcfi_free(p_trace.entries);


p_ed_enforce_pcfi_out:

   return p_not_valid;
}

int p_ed_pcfi_validate_sp(struct task_struct *p_task, struct p_ed_process *p_orig, unsigned long p_sp) {

#ifdef CONFIG_X86_32
   return P_LKRG_SUCCESS;
#else
   unsigned long p_stack = 0;
   unsigned long p_stack_offset;
   int p_not_valid = 0;

   if (!P_CTRL(p_pcfi_validate)) {
      return P_LKRG_SUCCESS;
   }

   if (p_orig) {
      p_stack = (unsigned long)p_orig->p_ed_task.p_stack;
      p_task = p_orig->p_ed_task.p_task;
   }

#define P_PRINT_LOG_STACK(what) \
   if (P_CTRL(p_log_level) >= P_LOG_WATCH) \
      p_print_log(P_LOG_ALERT, "DETECT: Task: Stack " what " (base 0x%lx, sp 0x%lx) for pid %u, name %s", \
         p_stack, p_sp, task_pid_nr(p_task), p_task->comm); \
   else \
      p_print_log(P_LOG_ALERT, "DETECT: Task: Stack " what " for pid %u, name %s", \
         task_pid_nr(p_task), p_task->comm); \
   p_not_valid = 1; \

   /*
    * Validate alignment
    */
   if (unlikely(p_sp & (sizeof(long) - 1))) {
      P_PRINT_LOG_STACK("pointer misaligned")
   }

   if (!p_stack) {
      return p_not_valid ? P_LKRG_GENERAL_ERROR : P_LKRG_SUCCESS;
   }

   /*
    * Validate stack base
    */
   if (unlikely((p_sp & ~(THREAD_SIZE - 1)) != (p_stack & ~(THREAD_SIZE - 1)))) {
      P_PRINT_LOG_STACK("base invalid")
   }

   /*
    * Validate if stack is coming from the valid range (CONFIG_VMAP_STACK)
    */

   // TODO

   /*
    * Validate current size of the stack.
    */

   p_stack_offset = p_sp - p_stack;
   if (unlikely(p_stack_offset >= THREAD_SIZE)) {
      P_PRINT_LOG_STACK("size mismatch")
   }

   return p_not_valid ? P_LKRG_GENERAL_ERROR : P_LKRG_SUCCESS;
#endif
}


int p_exploit_detection_init(void) {

   const struct p_functions_hooks *p_fh_it;

   p_global_off_cookie = (unsigned long)get_random_long();
   p_global_cnt_cookie = (unsigned long)get_random_long();

   p_global_off_cookie |= P_NORMALIZE_LONG;
   p_global_cnt_cookie |= P_NORMALIZE_LONG;
   p_global_cnt_cookie &= P_MASK_COUNTER;

   if (p_ed_pcfi_cache_init()) {
      p_print_log(P_LOG_FATAL, "Can't initialize pCFI cache");
      return P_LKRG_GENERAL_ERROR;
   }

   if (p_ed_wq_valid_cache_init()) {
      p_print_log(P_LOG_FATAL, "Can't initialize cache for data integrity WQ");
      return P_LKRG_GENERAL_ERROR;
   }

   P_SYM_INIT(__kernel_text_address)

#ifdef CONFIG_SECURITY_SELINUX
#if (!defined(RHEL_RELEASE_CODE) && LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0)) || \
     (defined(RHEL_RELEASE_CODE) && RHEL_RELEASE_CODE < RHEL_RELEASE_VERSION(8, 3))
   P_SYM_INIT(selinux_enabled)
#endif
   // SELinux information
#ifdef P_SELINUX_VERIFY
   if (p_selinux_state_init()) {
      p_print_log(P_LOG_FATAL, "Can't initialize SELinux");
      return P_LKRG_GENERAL_ERROR;
   }
#elif defined(CONFIG_GCC_PLUGIN_RANDSTRUCT)
   p_print_log(P_LOG_ISSUE, "Can't enforce SELinux validation (CONFIG_GCC_PLUGIN_RANDSTRUCT detected)");
#endif
#if (!defined(RHEL_RELEASE_CODE) && LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0)) || \
     (defined(RHEL_RELEASE_CODE) && RHEL_RELEASE_CODE < RHEL_RELEASE_VERSION(8, 3))
   p_ed_guard_globals.p_selinux.p_selinux_enabled = *P_SYM(p_selinux_enabled);
#endif
   p_lkrg_counter_lock_init(&p_ed_guard_globals.p_selinux_lock);
#endif

   if (init_ed_task_cache()) {
      p_print_log(P_LOG_FATAL, "Can't initialize task cache");
      return P_LKRG_GENERAL_ERROR;
   }

   // Dump processes and threads
   p_dump_ed_tasks();

   for (p_fh_it = p_functions_hooks_array; p_fh_it->name != NULL; p_fh_it++) {
      if (p_fh_it->install(p_fh_it->is_isra_safe)) {
         if (!p_fh_it->p_fatal) {
             p_print_log(P_LOG_ISSUE, "%s", p_fh_it->p_error_message);
             continue;
         }
         p_print_log(P_LOG_FATAL, "Can't hook %s", p_fh_it->name);
         return P_LKRG_GENERAL_ERROR;
      }
   }

   return P_LKRG_SUCCESS;

p_sym_error:
   return P_LKRG_GENERAL_ERROR;
}


void p_exploit_detection_exit(void) {

   const struct p_functions_hooks *p_fh_it;

#if !defined(P_LKRG_DEBUG_BUILD)
   lockdep_off();
#endif

   for (p_fh_it = p_functions_hooks_array; p_fh_it->name != NULL; p_fh_it++) {
      p_fh_it->uninstall();
   }

   /* Delete cache for ED wq validation */
   p_ed_wq_valid_cache_delete();
   /* Delete cache for ED CFI validation */
   p_ed_pcfi_cache_delete();
   /* Before deleting cache i should clean each entry! */
   destroy_ed_task_cache();

#if !defined(P_LKRG_DEBUG_BUILD)
   lockdep_on();
#endif

   p_print_log(P_LOG_WATCH, "kmem_cache 'p_ed_pids' destroyed");
}
