From cc34df83d597f620aee521e8ed8c38ecc5e1b180 Mon Sep 17 00:00:00 2001
From: Patrick Daly <pdaly@codeaurora.org>
Date: Wed, 22 Apr 2015 18:04:21 -0700
Subject: [PATCH] ldrpv2: slabinfo: Check poison markers on all slub objects

Validates redzone, padding, and poison markers for both allocated
and free objects.

Example output format:

Poison overwritten
INFO: 0xffffffc090546a30-0xffffffc090546a3f. First byte 0x0 instead of 0x6b
INFO: Object 0xffffffc090546a00 @offset=%a00 fp=0xffffffc090546f00

Bytes b4
ffffffc0905469f0: 5a5a 5a5a 5a5a 5a5a 5a5a 5a5a 5a5a 5a5a  ZZZZZZZZZZZZZZZZ
Object
ffffffc090546a00: 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b  kkkkkkkkkkkkkkkk
ffffffc090546a10: 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b  kkkkkkkkkkkkkkkk
ffffffc090546a20: 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b  kkkkkkkkkkkkkkkk
ffffffc090546a30: 006b 006b 600b 6b00 6b00 006b 6b00 6b00  .k.k`.k.k..kk.k.
ffffffc090546a40: 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b  kkkkkkkkkkkkkkkk
ffffffc090546a50: 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b  kkkkkkkkkkkkkkkk
ffffffc090546a60: 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b  kkkkkkkkkkkkkkkk
ffffffc090546a70: 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b  kkkkkkkkkkkkkkkk
ffffffc090546a80: 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b  kkkkkkkkkkkkkkkk
ffffffc090546a90: 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b  kkkkkkkkkkkkkkkk
ffffffc090546aa0: 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b  kkkkkkkkkkkkkkkk
ffffffc090546ab0: 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b  kkkkkkkkkkkkkkkk
ffffffc090546ac0: 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b 6b6b  kkkkkkkkkkkkkkkk
ffffffc090546ad0: 6b6b 6b6b 6b6b 6ba5                      kkkkkkk.

Change-Id: I2f784188f6f6990b1d3399d79f7ab9ef635e4835
---
 linux-ramdump-parser-v2/parsers/slabinfo.py | 154 ++++++++++++++++++++
 1 file changed, 154 insertions(+)
 mode change 100644 => 100755 linux-ramdump-parser-v2/parsers/slabinfo.py

diff --git a/linux-ramdump-parser-v2/parsers/slabinfo.py b/linux-ramdump-parser-v2/parsers/slabinfo.py
old mode 100644
new mode 100755
index eb8604f..79dcae9
--- a/linux-ramdump-parser-v2/parsers/slabinfo.py
+++ b/linux-ramdump-parser-v2/parsers/slabinfo.py
@@ -15,6 +15,17 @@ from mm import page_address, pfn_to_page
 from print_out import print_out_str
 from parser_util import register_parser, RamParser
 
+SLAB_RED_ZONE = 0x400
+SLAB_POISON = 0x800
+SLAB_STORE_USER = 0x10000
+OBJECT_POISON = 0x80000000
+
+SLUB_RED_INACTIVE = 0xbb
+SLUB_RED_ACTIVE = 0xcc
+POISON_INUSE = 0x5a
+POISON_FREE = 0x6b
+POISON_END = 0xa5
+
 class kmem_cache(object):
     def __init__(self, ramdump, addr):
         self.valid = False
@@ -136,6 +147,110 @@ class Slabinfo(RamParser):
             n_objects = (count >> 16) & 0xFFFF
             return n_objects
 
+    def print_section(self, text, addr, length, out_file):
+        out_file.write('{}\n'.format(text))
+        output = self.ramdump.hexdump(addr, length)
+        out_file.write(output)
+
+    def print_trailer(self, s, page, p, out_file):
+        addr = page_address(self.ramdump, page)
+
+        if self.ramdump.is_config_defined('CONFIG_SLUB_DEBUG_ON'):
+            self.print_track(self.ramdump, s.addr, p, 0, out_file)
+            self.print_track(self.ramdump, s.addr, p, 1, out_file)
+
+        out_file.write('INFO: Object 0x{:x} @offset=0x{:x} fp=0x{:x}\n\n'.format(
+            p, p - addr, self.get_free_pointer(self.ramdump, s, p)))
+
+        if (p > addr + 16):
+            self.print_section('Bytes b4 ', p - 16, 16, out_file)
+
+        self.print_section('Object ', p, min(s.object_size, 4096), out_file)
+        if (s.flags & SLAB_RED_ZONE):
+            self.print_section('Redzone ', p + s.object_size,
+                s.inuse - s.object_size, out_file)
+
+        if (s.offset):
+            off = s.offset + self.ramdump.sizeof('void *')
+        else:
+            off = s.inuse
+
+        if (s.flags & SLAB_STORE_USER):
+            off += 2 * self.ramdump.sizeof('struct track')
+
+        if (off != s.size):
+            # Beginning of the filler is the free pointer
+            self.print_section('Padding ', p + off, s.size - off, out_file)
+
+    def memchr_inv(self, addr, value, size):
+        data = self.read_byte_array(addr, size)
+        if data is None:
+            return 0
+        for i in range(len(data)):
+            if data[i] != value:
+                return i + addr
+        return 0
+
+    def check_bytes_and_report(self, s, page, object, what, start,
+             value, bytes, out_file):
+        fault = self.memchr_inv(start, value, bytes)
+        if (not fault):
+            return True
+
+        end = start + bytes
+        while (end > fault and (self.read_byte_array(end - 1, 1)[0]) == value):
+            end -= 1
+
+        out_file.write('{0} overwritten\n'.format(what))
+        out_file.write('INFO: 0x{:x}-0x{:x}. First byte 0x{:x} instead of 0x{:x}\n'.format(
+            fault, end - 1, self.ramdump.read_byte(fault), value))
+
+        self.print_trailer(s, page, object, out_file)
+        return False
+
+    def check_pad_bytes(self, s, page, p, out_file):
+        off = s.inuse
+
+        if (s.offset):
+            # Freepointer is placed after the object
+            off += self.ramdump.sizeof('void *')
+
+        if (s.flags & SLAB_STORE_USER):
+            # We also have user information there
+            off += 2 * self.ramdump.sizeof('struct track')
+
+        if (s.size == off):
+            return True
+
+        return self.check_bytes_and_report(s, page, p, 'Object padding',
+            p + off, POISON_INUSE, s.size - off, out_file)
+
+    def check_object(self, s, page, object, val, out_file):
+        p = object
+        endobject = object + s.object_size
+
+        if (s.flags & SLAB_RED_ZONE):
+            if (not self.check_bytes_and_report(s, page, object, 'Redzone',
+                endobject, val, s.inuse - s.object_size, out_file)):
+                return
+        else:
+            if ((s.flags & SLAB_POISON) and s.object_size < s.inuse):
+                self.check_bytes_and_report(s, page, p, 'Alignment padding',
+                    endobject, POISON_INUSE,
+                    s.inuse - s.object_size, out_file)
+
+        if (s.flags & SLAB_POISON):
+            if (val != SLUB_RED_ACTIVE and (s.flags & OBJECT_POISON) and
+                (not self.check_bytes_and_report(s, page, p, 'Poison', p,
+                    POISON_FREE, s.object_size - 1, out_file) or
+                not self.check_bytes_and_report(s, page, p, 'Poison',
+                    p + s.object_size - 1, POISON_END, 1, out_file))):
+                return
+
+            # check_pad_bytes cleans up on its own.
+            self.check_pad_bytes(s, page, p, out_file)
+
+
     def print_slab(self, ramdump, slab_start, slab, page, out_file, map_fn):
         p = slab_start
         if page is None:
@@ -200,6 +315,12 @@ class Slabinfo(RamParser):
             self.print_track(self.ramdump, slab, p, 0, out_file)
             self.print_track(self.ramdump, slab, p, 1, out_file)
 
+    def print_check_poison(self, p, free, slab, page, out_file):
+        if free:
+            self.check_object(slab, page, p, SLUB_RED_INACTIVE, out_file)
+        else:
+            self.check_object(slab, page, p, SLUB_RED_ACTIVE, out_file)
+
     # based on validate_slab_cache. Currently assuming there is only one numa node
     # in the system because the code to do that correctly is a big pain. This will
     # need to be changed if we ever do NUMA properly.
@@ -255,3 +376,36 @@ class Slabinfo(RamParser):
         slab_out = self.ramdump.open_file('slabs.txt')
         self.validate_slab_cache(slab_out, self.print_all_objects)
         print_out_str('---wrote slab information to slabs.txt')
+
+@register_parser('--slabpoison', 'check slab poison', optional=True)
+class Slabpoison(Slabinfo):
+    """Note that this will NOT find any slab errors which are printed out by the
+    kernel, because the slab object is removed from the freelist while being
+    processed"""
+
+    # since slabs are relatively "packed", caching has a large
+    # performance benefit
+    def read_byte_array(self, addr, size):
+        page_addr = addr & -0x1000
+        end_page_addr = (addr + size) & -0x1000
+        # in cache
+        if page_addr == end_page_addr and page_addr == self.cache_addr:
+            idx = addr - self.cache_addr
+            return self.cache[idx:idx + size]
+        # accessing only one page
+        elif page_addr == end_page_addr:
+            fmtstr = '<{}B'.format(4096)
+            self.cache = self.ramdump.read_string(page_addr, fmtstr)
+            self.cache_addr = page_addr
+            idx = addr - self.cache_addr
+            return self.cache[idx:idx + size]
+        else:
+            fmtstr = '<{}B'.format(size)
+            return self.ramdump.read_string(addr, fmtstr)
+
+    def parse(self):
+        self.cache = None
+        self.cache_addr = None
+        slab_out = self.ramdump.open_file('slabpoison.txt')
+        self.validate_slab_cache(slab_out, self.print_check_poison)
+        print_out_str('---wrote slab information to slabpoison.txt')
-- 
GitLab