#! /usr/bin/stap -gv

/* A systemtap emergency band-aid for CVE-2016-0728.
   fche@redhat.com & wmealing@redhat.com
*/


/* Default behavior: trace but don't fix problem. */ 
global trace_p = 1 /* or else:  stap -G trace_p=0 */
global fix_p = 0   /* or else:  stap -G fix_p=1 */


probe kernel.statement("join_session_keyring@*+44") !,
      kernel.function("join_session_keyring").label("error2")
{
  /* NB: if the DWARF debuginfo were more perfect, we should be able
     to refer directly to $keyring/$new, local variables still
     technically in scope.  On some kernels/gcc combinations, that
     works fine.  On others, it doesn't, so this script tries to 
     support both. */
  if (@defined($keyring))
    keyring = $keyring
  else {
    if (! warned_keyrings_p++)
      warn("Using find_keyring_by_name $return heuristic for $keyring")
    keyring = keyrings[tid()]
  }
  if (@defined($new))
    new = $new
  else {
    if (! warned_news_p++)
      warn("Using prepare_creds $return heuristic for $new")
    new = news[tid()]
  }

  /* The actual security band-aid payload. */
  if (keyring == @cast(new,"struct cred")->session_keyring) {
    if (trace_p) printf("%s[%d] rejoin keyring %s %p %s\n",
                        execname(), tid(), $name$,
                        keyring,
                        @cast(keyring,"struct key")->usage$$)
    if (fix_p) do_key_put(keyring) 
    if (fix_p && trace_p) printf("-> %p %s\n",
                                 keyring,
                                 @cast(keyring,"struct key")->usage$$)

  }
}

function do_key_put(ptr)
%{
  if (STAP_ARG_ptr != 0)
    key_put ((struct key *) STAP_ARG_ptr);
%}  


/* We cache the last $keyring value for this thread.  Relying on this
   table instead of direct access to $keyring at the
   join_session_keyring function label is undesirable.  This is
   because we don't have a very good way of keeping this table clean
   (to remove old entries).  (Extraordinary measures could include
   catching thread deaths, or returns from *callers* of
   find_keyring_by_name.)  So what we do here instead is label keyrings%
   as an auto-wrapping array, so *old* entries will be reused. */
global keyrings%, warned_keyrings_p
probe kernel.function("find_keyring_by_name").return
{
  keyrings[tid()] = $return
}

/* And same for the $new variable.  :-( */
global news%, warned_news_p
probe kernel.function("prepare_creds").return
{
  news[tid()] = $return
}

/* Disable the automatic dumping of these globals. */
probe never
{
  println(keyrings[0])
  println(news[0])
  println(warned_keyrings_p+1)
  println(warned_news_p+1)
}
