From 2a1f4f8ef2ec4143ebc86f2933a60fc75ba4fe3d Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 28 Feb 2019 14:29:45 -0500
Subject: [PATCH 016/169] FIXME: example checkers

---
 checkers/address_of_function.sm | 14 +++++++
 checkers/buffer_overflow.sm     | 75 +++++++++++++++++++++++++++++++++
 checkers/fd_checker.sm          | 26 ++++++++++++
 checkers/fileptr.sm             | 17 ++++++++
 checkers/malloc_checker.sm      | 74 +++++++++++++++++++++++++++++++++
 checkers/points_to_stack.sm     | 60 +++++++++++++++++++++++++++
 checkers/python.sm              | 61 +++++++++++++++++++++++++++
 checkers/signal_handler.sm      | 58 ++++++++++++++++++++++++++
 checkers/sizeof_allocation.sm   | 91 +++++++++++++++++++++++++++++++++++++++++
 checkers/taint.sm               | 71 ++++++++++++++++++++++++++++++++
 10 files changed, 547 insertions(+)
 create mode 100644 checkers/address_of_function.sm
 create mode 100644 checkers/buffer_overflow.sm
 create mode 100644 checkers/fd_checker.sm
 create mode 100644 checkers/fileptr.sm
 create mode 100644 checkers/malloc_checker.sm
 create mode 100644 checkers/points_to_stack.sm
 create mode 100644 checkers/python.sm
 create mode 100644 checkers/signal_handler.sm
 create mode 100644 checkers/sizeof_allocation.sm
 create mode 100644 checkers/taint.sm

diff --git a/checkers/address_of_function.sm b/checkers/address_of_function.sm
new file mode 100644
index 0000000..9917ab8
--- /dev/null
+++ b/checkers/address_of_function.sm
@@ -0,0 +1,14 @@
+/* function not invoked */
+sm address_of_function {
+   /* global state */
+   decl any_function_ptr fnptr;
+
+   fnptr.*:
+      { fnptr } ->
+          {{
+             # how to properly characterize?
+             if is_simple_reference(fnptr):
+                 error('usage of %s when you probably meant %s()')
+                 # does this have a CWE?
+           }};
+}
\ No newline at end of file
diff --git a/checkers/buffer_overflow.sm b/checkers/buffer_overflow.sm
new file mode 100644
index 0000000..47c2470
--- /dev/null
+++ b/checkers/buffer_overflow.sm
@@ -0,0 +1,75 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This is free software: you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+
+   Checker to detect typos of the form:  malloc(sizeof(WRONG_TYPE))
+*/
+sm buffer_overflow {
+  state decl char * ptr;
+  decl any_pointer other_ptr;
+  decl any_expr nmemb;
+  decl any_expr sz;
+
+  /* We will use:
+       "size" for the size of the allocated buffer (when known),
+       "strlen" for the length of the string (not including NUL character),
+       when known
+     
+     None means "unknown" in both cases
+  */
+  {{
+      # Utility methods for getting at current state and size, given
+      # that the initial state has no key/value pairs set
+      def get_size(state):
+          if hasattr(state, 'size'):
+             return state.size
+          return None
+
+      def get_strlen(state):
+          if hasattr(state, 'strlen'):
+             return state.strlen
+          return None
+  }}
+
+  ptr.all:
+    /* Various allocators: */
+      /* libc: */
+      { ptr = malloc(sz) }
+    | { ptr = realloc(other_ptr, sz) }
+      /* libpython: */
+    | { ptr = PyMem_Malloc(sz) }
+    | { ptr = PyMem_Realloc(other_ptr, sz) }
+      /* glib: */
+    | { ptr = g_malloc(sz) }
+    | { ptr = g_malloc0(sz) }
+    | { ptr = g_realloc(other_ptr, sz) }
+    | { ptr = g_try_malloc(sz) }
+    | { ptr = g_try_malloc0(sz) }
+    | { ptr = g_try_realloc(other_ptr, sz) }
+      /* etc */
+      -> {{
+    set_state('ptr', size=sz, strlen=None)
+         }};
+
+    | {sz = strlen(ptr) }
+      -> {{
+          set_state('ptr',
+                    size=get_size(state),
+                    strlen=sz)
+        }}
+      /* etc */
+}
diff --git a/checkers/fd_checker.sm b/checkers/fd_checker.sm
new file mode 100644
index 0000000..cef881e
--- /dev/null
+++ b/checkers/fd_checker.sm
@@ -0,0 +1,26 @@
+/*
+    Work-in-progress checker for file-descriptor APIs
+ */
+        
+sm fd_checker {
+   state decl int fd;
+   decl any_expr buf, count;
+   
+   /* open() and creat return a new fd, or -1, setting errno */
+   fd.all:
+       { fd = open() } -> fd.unknown
+     | { fd = create() } -> fd.unknown
+     ;
+
+   fd.unknown:
+       { fd == -1 } -> true=fd.error, false=fd.open
+     | { fd != -1 } -> true=fd.open, false=fd.error
+     ;
+
+   fd.open:
+       { close(fd) } -> fd.maybeclosed;
+
+   fd.maybeclosed:
+       { close(fd) } -> {error('double close of %s' % fd)}
+
+}
diff --git a/checkers/fileptr.sm b/checkers/fileptr.sm
new file mode 100644
index 0000000..ae1a9e1
--- /dev/null
+++ b/checkers/fileptr.sm
@@ -0,0 +1,17 @@
+sm fileptr_checker {
+   state decl FILE* fp;
+
+   decl any_expr path, mode;
+   decl any_expr fd;
+   decl any_expr stream;
+   
+   fp.all:
+       { fp = fopen(path, mode) } -> fp.unchecked
+     | { fp = fdopen(fd, mode) } -> fp.unchecked
+     | { fp = freopen(path, mode, stream) } -> fp.unchecked
+     ;
+
+   fp.unchecked:
+       { fclose(fp) } -> { error('use of possibly NULL %s' % fp) };
+
+}
diff --git a/checkers/malloc_checker.sm b/checkers/malloc_checker.sm
new file mode 100644
index 0000000..39ab433
--- /dev/null
+++ b/checkers/malloc_checker.sm
@@ -0,0 +1,74 @@
+/*
+    Work-in-progress checker for malloc/free usage
+ */
+sm malloc_checker {
+  stateful decl any_pointer ptr;
+
+  ptr.*:
+    { ptr = malloc() } ->  ptr.unchecked;
+
+  ptr.*:
+    { ptr = 0 } ->  ptr.null;
+
+  ptr.start, ptr.unchecked:
+      { ptr == 0 } -> true=ptr.null, false=ptr.nonnull
+    | { ptr != 0 } -> true=ptr.nonnull, false=ptr.null
+    ;
+
+  ptr.free:
+      { ptr == 0 } -> true=ptr.null
+    | { ptr != 0 } -> false=ptr.null
+    ;
+
+  ptr.unchecked:
+    { *ptr }
+      -> {{
+            error('dereference of possibly-NULL pointer %s' % ptr,
+                  // "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
+                  cwe='CWE-690')
+         }}, ptr.nonnull;
+
+  ptr.null:
+    { *ptr }
+      -> {{
+            error('dereference of NULL pointer %s' % ptr,
+                  // "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
+                  cwe='CWE-690')
+         }}, ptr.stop;
+
+  ptr.start, ptr.unchecked, ptr.null, ptr.nonnull:
+    { free(ptr) } -> ptr.free;
+
+  ptr.free:
+      { free(ptr) }
+          -> {{
+                error('double-free of %s' % ptr,
+                      // "CWE-415: Double Free"
+                      cwe='CWE-415')
+             }}, ptr.stop
+    | { ptr }
+          -> {{
+                error('use-after-free of %s' % ptr,
+                      // "CWE-416: Use After Free"
+                      cwe='CWE-416')
+             }}, ptr.stop
+    ;
+
+  ptr.unchecked:
+    @arg_must_not_be_null
+      -> {{
+            error('possibly-NULL pointer %s passed as argument %i to %s'
+                  % (ptr, argnumber, function),
+                  // "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
+                  cwe='CWE-690')
+          }}, ptr.nonnull;
+
+  ptr.null:
+    @arg_must_not_be_null
+      -> {{
+            error('NULL pointer %s passed as argument %i to %s'
+                  % (ptr, argnumber, function),
+                  // "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
+                  cwe='CWE-690')
+         }}, ptr.stop;
+}
diff --git a/checkers/points_to_stack.sm b/checkers/points_to_stack.sm
new file mode 100644
index 0000000..63e0058
--- /dev/null
+++ b/checkers/points_to_stack.sm
@@ -0,0 +1,60 @@
+/*
+   Copyright 2013 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2013 Red Hat, Inc.
+
+   This is free software: you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+/*
+   Checker for CWE-562: Return of Stack Variable Address
+
+   This will only detect simple cases: it doesn't attempt to track e.g.
+   pointers within a return struct, or within a reference graph.
+
+   Also, it potentially exhibits false positives for the case where
+   a pointer to a variable deeper in the stack is returned by a frame
+   higher up, where the usage of the area is within the lifetime of
+   the stack frame holding it.
+
+ */
+sm points_to_stack {
+   stateful decl any_pointer ptr;
+   decl any_variable var;
+
+   {{
+        def is_on_stack(var):
+           import gcc
+
+           if isinstance(var.context, gcc.FunctionDecl):
+               if not var.static:
+                  return True
+   }}
+
+   ptr.*:
+      { ptr = &var } ->
+        {{
+            if is_on_stack(var):
+                set_state('ptr.points_to_stack', var_pointed_to=var)
+        }};
+
+   ptr.points_to_stack:
+       { return ptr }
+         -> {{
+                error('return of "%s" returns address within stack of local variable "%s"'
+                      % (ptr, state.var_pointed_to),
+                      # "CWE-562: Return of Stack Variable Address":
+                      cwe='CWE-562')
+            }};
+}
\ No newline at end of file
diff --git a/checkers/python.sm b/checkers/python.sm
new file mode 100644
index 0000000..86b1b0a
--- /dev/null
+++ b/checkers/python.sm
@@ -0,0 +1,61 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This is free software: you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+/* TODO: autogenerate this checker from the API description? */
+/* Is this actually implementable using the sm framework? */
+
+sm python {
+  state decl expr ptr;
+  decl any_pointer other_ptr;
+  decl any_expr sz;
+
+  ptr.all:
+      { ptr = PyList_New(sz) }
+        -> {{
+                set_state('ptr.unchecked_newref',
+                          ob_type='PyList_Type')
+                # How much can we link states?
+                set_field_state('ob_refcnt',
+                                'newref')
+                # Other idea:
+                set_field_fact(ptr, 'ob_refcnt', '>=', 1)
+                # (this can be manipulated Py_INCREF and Py_DECREF sanely, I think)
+                # what about the tp_dealloc call clearing things though?
+           }}
+
+  ptr.unchecked_newref:
+      /* FIXME: we'll want to inherit the kv pairs in the new states: */
+      { ptr == 0 } -> true=ptr.null, false=ptr.nonnull_newref
+    | { ptr != 0 } -> true=ptr.nonnull_rewref, false=ptr.null
+    | { *ptr } -> {{ warning('use of possibly-NULL pointer %s' % ptr) }}, ptr.nonnull_newref
+    ;
+
+  ptr.null:
+    { *ptr } -> {{ error('use of NULL pointer %s' % ptr) }}
+
+  ptr.unchecked_newref, ptr.nonnull_newref:
+      $leaked$ -> {{ error('leak of %s' % ptr) }};
+
+  /* how to handle Py_INCREF and Py_DECREF ? 
+       Idea: track the ob_refcnt as an entirely separate checker, with
+       states on ob_refcnt fields?
+       (potentially have linkage between checkers?)
+  */
+  ptr.nonnull_newref
+}
diff --git a/checkers/signal_handler.sm b/checkers/signal_handler.sm
new file mode 100644
index 0000000..8ef96de
--- /dev/null
+++ b/checkers/signal_handler.sm
@@ -0,0 +1,58 @@
+/* 
+  SIG30-C: https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers
+
+   I *think* this needs to be two-pass: find the signal handlers, then run
+   a normal state/vs handler state thing.
+
+*/
+
+sm find_signal_handlers {
+   stateful decl any_function_pointer fnptr;
+   decl any_expr signum;
+
+   {{
+        signal_handlers = set()
+   }}
+
+   fnptr.*:
+      { signal(signum, fnptr) }
+        -> {{
+              raise foo
+              # this isn't getting reached; I *think* it's because "handler" isn't in the smexprs
+              # scope for the Context
+
+              signal_handlers.add(fnptr)
+              print('signal_handlers: %r' % signal_handlers)
+           }};
+}
+
+sm async_unsafe {
+   /* (global state) */
+   stateful decl any_expr FIXME;
+
+    /* 
+      need to somehow wire in async calls to the functions in
+      signal_handlers into the supergraph
+      Or to just prime the states somehow:
+     */
+   {{
+       """
+       for fn in signal_handlers:
+           ep = add_entrypoint(fn,
+                               entrydesc='called as signal handler',
+                               exitdesc='returning from signal handler')
+           ep.set_state('within_signal_handler')
+           # instance of an EntryPoint class
+           """
+   }}
+
+/* FIXME:
+   within_signal_handler:
+      {{ fprintf(arg, ...) }}
+      -> {{
+            error('use of signal-unsafe function %s from within a signal-handler' % function,
+                  other_ids=[Id('SIG30-C',
+                                 url='https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers')])
+         }};
+*/
+}
\ No newline at end of file
diff --git a/checkers/sizeof_allocation.sm b/checkers/sizeof_allocation.sm
new file mode 100644
index 0000000..ca63e1d
--- /dev/null
+++ b/checkers/sizeof_allocation.sm
@@ -0,0 +1,91 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This is free software: you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+
+   Checker to detect typos of the form:  malloc(sizeof(WRONG_TYPE))
+*/
+sm sizeof_allocation {
+  stateful decl any_pointer ptr;
+  decl any_pointer other_ptr;
+  decl any_expr nmemb;
+  decl any_expr sz;
+
+  {{
+    def is_known_int(var):
+        # We can only run the checker on constant integer expressions
+        import gcc
+        return isinstance(var.gccexpr, gcc.IntegerCst)
+
+    def check_size(ptr, allocated_size):
+        import gcc
+        type_pointed_to = ptr.type.dereference
+        if not isinstance(type_pointed_to, gcc.VoidType):
+            required_size = type_pointed_to.sizeof
+            if allocated_size < required_size:
+                error("allocation too small: pointer to %s (%i bytes)"
+                      " initialized with allocation of %i bytes"
+                      % (type_pointed_to, required_size, allocated_size),
+                      cwe="CWE-131") # "Incorrect Calculation of Buffer Size"
+
+        # Handle cases where the cast happens on another line:
+        set_state("ptr.sized", size=int(allocated_size))
+  }}
+
+  ptr.*:
+    /* Various allocators: */
+      /* libc: */
+      { ptr = malloc(sz) }
+    | { ptr = realloc(other_ptr, sz) }
+      /* libpython: */
+    | { ptr = PyMem_Malloc(sz) }
+    | { ptr = PyMem_Realloc(other_ptr, sz) }
+      /* glib: */
+    | { ptr = g_malloc(sz) }
+    | { ptr = g_malloc0(sz) }
+    | { ptr = g_realloc(other_ptr, sz) }
+    | { ptr = g_try_malloc(sz) }
+    | { ptr = g_try_malloc0(sz) }
+    | { ptr = g_try_realloc(other_ptr, sz) }
+      /* etc */
+      -> {{
+            if is_known_int(sz):
+                check_size(ptr, sz)
+         }};
+
+  ptr.*:
+      /* libc: */
+      { ptr = calloc(nmemb, sz) }
+      /* glib: */
+    | { ptr = g_malloc_n(nmemb, sz) }
+    | { ptr = g_malloc0_n(nmemb, sz) }
+    | { ptr = g_realloc_n(other_pointer, nmemb, sz) }
+    | { ptr = g_try_malloc_n(nmemb, sz) }
+    | { ptr = g_try_malloc0_n(nmemb, sz) }
+    | { ptr = g_try_realloc_n(other_pointer, nmemb, sz) }
+      -> {{
+            if is_known_int(nmemb):
+                if is_known_int(sz):
+                    check_size(ptr, int(nmemb) * int(sz))
+         }};
+
+  ptr.sized:
+     { other_ptr = ptr } ->
+       {{
+            check_size(other_ptr, allocated_size=state.size)
+       }};
+
+}
diff --git a/checkers/taint.sm b/checkers/taint.sm
new file mode 100644
index 0000000..7b305ab
--- /dev/null
+++ b/checkers/taint.sm
@@ -0,0 +1,71 @@
+sm taint {
+  stateful decl any_expr x;
+
+  decl any_expr y;
+  decl any_expr arr;
+  decl any_expr size;
+  decl any_expr nmemb;
+  decl any_expr stream;
+
+  /* Patterns that detect upper-bound and lower-bound checks: */
+  pat check_ub { x < y } | { x <= y };
+  pat check_lb { x > y } | { x >= y };
+  pat check_eq { x == y };
+  pat check_ne { x != y };
+
+  /* Sources of tainted data: */
+  x.*:
+    /* reading data from files: */
+      {fread(x, size, nmemb, stream)} -> x.tainted
+    | {read(fd, x, count)} -> x.tainted
+    ;
+
+  /*
+    When a tainted value is compared, mark it as now having either
+    an upper-bound, or a lower-bound:
+  */
+  x.tainted:
+      check_ub -> true=x.has_ub, false=x.has_lb
+    | check_lb -> true=x.has_lb, false=x.has_ub
+    ;
+
+  /*
+    If a value has only one bound, check to see if it gains the other bound:
+  */
+   x.has_lb:
+      check_ub -> true=x.ok
+    | check_lb -> false=x.ok
+    ;
+   x.has_ub:
+      check_ub -> false=x.ok
+    | check_lb -> true=x.ok
+    ;
+
+  /* If a value is successfully compared against a specific value, it's OK */
+  x.tainted, x.has_lb, x.has_ub:
+      check_eq -> true=x.ok
+    | check_ne -> false=x.ok
+    ;
+
+  /* Detect when a tainted value is used in a dangerous way: */
+  x.tainted:
+      { arr[x] } -> {{
+          error('use of tainted value %s in array lookup without bounds checking' % x,
+                cwe='CWE-129') # "Improper Validation of Array Index"
+        }};
+
+  x.has_lb:
+      { arr[x] } -> {{
+          error('use of tainted value %s in array lookup without upper-bound check' % x,
+                cwe='CWE-129') # "Improper Validation of Array Index"
+        }};
+
+  /* Unsigned types have an implicit lower bound: */
+  x.has_ub:
+      { arr[x] } -> {{
+          if not x.type.unsigned:
+             error('use of tainted value %s in array lookup without lower-bound check' % x,
+                   cwe='CWE-129') # "Improper Validation of Array Index"
+        }};
+
+}
-- 
1.8.5.3

