/*
 * pi3's Linux kernel Runtime Guard
 *
 * Component:
 *  - Intercept SECCOMP policy update
 *
 * Notes:
 *  - Process SECCOMP Exploit Detection validation
 *
 * Caveats:
 *  - None
 *
 * Timeline:
 *  - Created: 18.XI.2017
 *
 * Author:
 *  - Adam 'pi3' Zabrocki (http://pi3.com.pl)
 *
 */

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

#if defined(P_LKRG_EXPLOIT_DETECTION_SECCOMP_H)

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)
static __always_inline void p_seccomp_filter_tsync(bool entry) {

   struct p_ed_process *kill_list = NULL, *p_tmp;
   struct task_struct *thread;
   unsigned long flags;

   /*
    * It's possible for there to be a race between an indirect seccomp filter
    * update due to SECCOMP_FILTER_FLAG_TSYNC and then a subsequent update
    * either directly from the task itself or another SECCOMP_FILTER_FLAG_TSYNC.
    * This can happen because flag_sync_thread is cleared outside of the
    * sighand-locked critical section where the seccomp filter thread sync takes
    * place (which is unavoidable since we can't reliably kprobe our way into
    * that critical section). As a result, this task could end up with a stale
    * seccomp shadow.
    *
    * To mitigate this, we acquire the sighand lock to keep a stable view of the
    * thread group and track the seccomp SECCOMP_FILTER_FLAG_TSYNC depth using
    * flag_sync_thread in order to know when the last seccomp thread sync is
    * truly complete, at which point we can clear the off flag for all threads.
    */
   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. Take the RCU
    * read lock once for the entire loop because it needs to be held until after
    * the sighand lock is released, in case there are tasks to kill.
    */
   rcu_read_lock();
   for_each_thread(current, thread) {
      p_tmp = ed_task_find_lock_rcu(thread);
      if (p_tmp) {
         if (entry) {
            /* Set the off flag once upon the first SECCOMP_FILTER_FLAG_TSYNC */
            if (!p_tmp->p_ed_task.p_sec.flag_sync_thread++)
               p_set_ed_process_off(p_tmp);
         } else {
            /* Clear the off flag once the tsync depth hits zero */
            if (p_tmp->p_ed_task.p_sec.flag_sync_thread == 1) {
               p_update_ed_process(p_tmp, 0);
               p_set_ed_process_on(p_tmp);
            }
            p_tmp->p_ed_task.p_sec.flag_sync_thread--;
         }
         /*
          * If the off flag was corrupted, then ed_task_unlock() will kill the
          * task. Calling send_sig_info() would deadlock here though, because it
          * needs the sighand lock which we're currently holding. Hence, defer
          * any killing until after we've released the sighand lock.
          */
         ed_task_unlock_defer_kill(p_tmp, &kill_list);
      }
   }
   spin_unlock_irqrestore(&current->sighand->siglock, flags);
   /* Process any kills that were deferred now that the sighand lock is free */
   ed_task_kill_deferred(kill_list);
   rcu_read_unlock();
}
#endif

/* per-instance private data */
struct p_seccomp_data {
   unsigned int filter_flags;
};

static __always_inline void p_handle_seccomp(struct p_seccomp_data *priv, struct pt_regs *p_regs, bool entry) {

   struct p_ed_process *p_tmp;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)
   /* Cache the flags passed to seccomp() so they can be checked on return */
   if (entry)
      priv->filter_flags = p_regs_get_arg2(p_regs);
   if (priv->filter_flags & SECCOMP_FILTER_FLAG_TSYNC)
      return p_seccomp_filter_tsync(entry);
#endif
   if ((p_tmp = ed_task_lock_current())) {
      if (entry) {
         p_set_ed_process_off(p_tmp);
      } else {
         p_update_ed_process(p_tmp, 0);
         p_set_ed_process_on(p_tmp);
      }
      ed_task_unlock(p_tmp);
   }
}

static int p_seccomp_entry(struct kretprobe_instance *p_ri, struct pt_regs *p_regs) {

   p_handle_seccomp((void *)p_ri->data, p_regs, true);
   return 0;
}

static int p_seccomp_ret(struct kretprobe_instance *p_ri, struct pt_regs *p_regs) {

   p_handle_seccomp((void *)p_ri->data, p_regs, false);
   return 0;
}

static struct lkrg_probe p_seccomp_probe = {
  .type = LKRG_KRETPROBE,
  .krp = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)
    .kp.symbol_name = "do_seccomp",
#else
    .kp.symbol_name = "prctl_set_seccomp",
#endif
    .handler = p_seccomp_ret,
    .entry_handler = p_seccomp_entry,
    .data_size = sizeof(struct p_seccomp_data),
  }
};

GENERATE_INSTALL_FUNC(seccomp)

#endif
