Kconfig A A+ F U U+
HARDENED_USERCOPY Y Y Y Y Y
SECURITY_DMESG_RESTRICT N Y N N N

A: Arch 5.1.8,A+: Arch Harden 5.1.11,F: Fedora 5.1.8,U: Ubuntu 5.0.0,U+: Ubuntu LTE 4.15.0


Preventing information leaks

Hardening usercopy()

To prevent the leakage from the kernel objects, the slab interface provides a way to specify the region of each object that is allowed for usercopy. For example, to protect task_struct from unintended leakage (e.g., out-of-bound read beyond the usercopy region of the heap object), a tuple of useroffset and userisze should be provided via kmem_cache_create_usercopy().

void __init fork_init(void) {
  ...
  // NB. calculate the offset and size of the fpu states
  task_struct_whitelist(&useroffset, &usersize);
  task_struct_cachep = kmem_cache_create_usercopy("task_struct",
                         arch_task_struct_size, align,
                         SLAB_PANIC|SLAB_ACCOUNT,
                         useroffset, usersize, NULL);
}

In task_struct, fpu state is allowed for usercopy (see, below), which are accessed from/to via the ptrace() syscall.

// @x86_64
//   task_struct_cachep->useroffset = 2624 :&fxregs_state
//   task_struct_cachep->usersize   =  960 :fpu_kernel_xstate_size

//
// arch_ptrace
// -> copy_regset_from_user
//   -> xfpregs_get()
//      -> user_regset_copyout()
//         -> copy_to_user()
int xfpregs_get(struct task_struct *target, const struct user_regset *regset,
                unsigned int pos, unsigned int count,
                void *kbuf, void __user *ubuf)
{
  struct fpu *fpu = &target->thread.fpu;
  ...
  return user_regset_copyout(&pos, &count, &kbuf, &ubuf,
                             &fpu->state.fxsave, 0, -1);
}

When HARDENED_USERCOPY enabled, copy_from/to_user performs various sanity checks, including the check for the useroffset and usersize.

// copy_from/to_user()
//   -> check_object_size()
//     -> check_heap_object()
//       -> __check_heap_object()
void __check_heap_object(const void *ptr, unsigned long n, struct page *page,
       bool to_user)
{
  struct kmem_cache *s;
  unsigned int offset;
  size_t object_size;

  /* NB. Fetch kmem_cache to find the object size/redzone. */
  s = page->slab_cache;

  /* NB. Reject if ptr is not possible to point to the page,
   * but the page is directly converted from ptr of its caller,
   * this path won't be taken in the current implementation. */
  if (ptr < page_address(page))
    usercopy_abort("SLUB object not in SLUB page?!", NULL,
             to_user, 0, n);

  /* Find offset within object. */
  offset = (ptr - page_address(page)) % s->size;

  /* Adjust for redzone and reject if within the redzone. */
  if (kmem_cache_debug(s) && s->flags & SLAB_RED_ZONE) {
    if (offset < s->red_left_pad)
      usercopy_abort("SLUB object in left red zone",
               s->name, to_user, offset, n);
    offset -= s->red_left_pad;
  }

  /* NB. Allow address range falling entirely within usercopy region.
  
   useroffset +   +-- offset (from ptr)
              |   v
              v   +--n-->|
              [   [      ]   ]
              |<--usersize--->|
   */
  if (offset >= s->useroffset &&
      offset - s->useroffset <= s->usersize &&
      n <= s->useroffset - offset + s->usersize)
    return;

  /*
   * If the copy is still within the allocated object, produce
   * a warning instead of rejecting the copy. This is intended
   * to be a temporary method to find any missing usercopy
   * whitelists.
   */
  object_size = slab_ksize(s);
  if (usercopy_fallback &&
      offset <= object_size && n <= object_size - offset) {
    usercopy_warn("SLUB object", s->name, to_user, offset, n);
    return;
  }

  usercopy_abort("SLUB object", s->name, to_user, offset, n);
}

There are a few other similar mitigation schemes to avoid such a mistake when performing a copy_to/from_user(). For example, if an object is stack allocated, then it checks if the object properly locates in the stack as well as in the proper frame of the stack, if the architecture provides a simple way to walk the stack frames (e.g., frame pointer).

// __check_object_size
//  -> check_stack_object
//    -> arch_within_stack_frames
static inline
int arch_within_stack_frames(const void * const stack,
                             const void * const stackend,
                             const void *obj, unsigned long len)
{
  const void *frame = NULL;
  const void *oldframe;

  // NB. return address of the caller
  oldframe = __builtin_frame_address(1);
  if (oldframe)
    // NB. return address of the caller's caller
    frame = __builtin_frame_address(2);

  /*
   * low ----------------------------------------------> high
   * [saved bp][saved ip][args][local vars][saved bp][saved ip]
   *                     ^----------------^
   *               allow copies only within here
   */
  while (stack <= frame && frame < stackend) {
    /*
     * If obj + len extends past the last frame, this
     * check won't pass and the next frame will be 0,
     * causing us to bail out and correctly report
     * the copy as invalid.
     */
    if (obj + len <= frame)
      // NB. 2 * sizeof(void*): frame pointer + return address
      return obj >= oldframe + 2 * sizeof(void *) ?
        GOOD_FRAME : BAD_STACK;
    oldframe = frame;
    frame = *(const void * const *)frame;
  }
  return BAD_STACK;
}

Restricting kernel pointers

dmesg command prints debug messages of the kernel buffer. However, the kernel message buffer sometimes contains sensitive information such as register values which is not allowed to users. This makes much easier for an attacker makes an exploit as in CVE-2018-17182. Thus DMESG_RESTRICT prevents unprivileged users from viewing those messages using dmesg command. When DMESG_RESTRICT is enabled, only users with system administration privileges are allowed to see the messages. When DMESG_RESTRICT is not enabled, every user can see the messages.

KPTR_RESTRICT works as similar to DMESG_RESTRICT. Kernel pointer is another sensitive information that might have chances of using by malicious users. When KPTR_RESTRICT is set to 1, %pK format specifier hides kernel pointers to unprivileged users by printing 0s. When KPTR_RESTRICT is set to 0, %pK works as same as %p which means there is no restriction on printing pointers. When KPTR_RESTRICT is set to 2, %pK hides pointers regardless of privileges.

References