188 lines
6.5 KiB
HTML
188 lines
6.5 KiB
HTML
|
<title>OS Bugs</title>
|
||
|
<html>
|
||
|
<head>
|
||
|
</head>
|
||
|
<body>
|
||
|
|
||
|
<h1>OS Bugs</h1>
|
||
|
|
||
|
<p>Required reading: Bugs as deviant behavior
|
||
|
|
||
|
<h2>Overview</h2>
|
||
|
|
||
|
<p>Operating systems must obey many rules for correctness and
|
||
|
performance. Examples rules:
|
||
|
<ul>
|
||
|
<li>Do not call blocking functions with interrupts disabled or spin
|
||
|
lock held
|
||
|
<li>check for NULL results
|
||
|
<li>Do not allocate large stack variables
|
||
|
<li>Do no re-use already-allocated memory
|
||
|
<li>Check user pointers before using them in kernel mode
|
||
|
<li>Release acquired locks
|
||
|
</ul>
|
||
|
|
||
|
<p>In addition, there are standard software engineering rules, like
|
||
|
use function results in consistent ways.
|
||
|
|
||
|
<p>These rules are typically not checked by a compiler, even though
|
||
|
they could be checked by a compiler, in principle. The goal of the
|
||
|
meta-level compilation project is to allow system implementors to
|
||
|
write system-specific compiler extensions that check the source code
|
||
|
for rule violations.
|
||
|
|
||
|
<p>The results are good: many new bugs found (500-1000) in Linux
|
||
|
alone. The paper for today studies these bugs and attempts to draw
|
||
|
lessons from these bugs.
|
||
|
|
||
|
<p>Are kernel error worse than user-level errors? That is, if we get
|
||
|
the kernel correct, then we won't have system crashes?
|
||
|
|
||
|
<h2>Errors in JOS kernel</h2>
|
||
|
|
||
|
<p>What are unstated invariants in the JOS?
|
||
|
<ul>
|
||
|
<li>Interrupts are disabled in kernel mode
|
||
|
<li>Only env 1 has access to disk
|
||
|
<li>All registers are saved & restored on context switch
|
||
|
<li>Application code is never executed with CPL 0
|
||
|
<li>Don't allocate an already-allocated physical page
|
||
|
<li>Propagate error messages to user applications (e.g., out of
|
||
|
resources)
|
||
|
<li>Map pipe before fd
|
||
|
<li>Unmap fd before pipe
|
||
|
<li>A spawned program should have open only file descriptors 0, 1, and 2.
|
||
|
<li>Pass sometimes size in bytes and sometimes in block number to a
|
||
|
given file system function.
|
||
|
<li>User pointers should be run through TRUP before used by the kernel
|
||
|
</ul>
|
||
|
|
||
|
<p>Could these errors have been caught by metacompilation? Would
|
||
|
metacompilation have caught the pipe race condition? (Probably not,
|
||
|
it happens in only one place.)
|
||
|
|
||
|
<p>How confident are you that your code is correct? For example,
|
||
|
are you sure interrupts are always disabled in kernel mode? How would
|
||
|
you test?
|
||
|
|
||
|
<h2>Metacompilation</h2>
|
||
|
|
||
|
<p>A system programmer writes the rule checkers in a high-level,
|
||
|
state-machine language (metal). These checkers are dynamically linked
|
||
|
into an extensible version of g++, xg++. Xg++ applies the rule
|
||
|
checkers to every possible execution path of a function that is being
|
||
|
compiled.
|
||
|
|
||
|
<p>An example rule from
|
||
|
the <a
|
||
|
href="http://www.stanford.edu/~engler/exe-ccs-06.pdf">OSDI
|
||
|
paper</a>:
|
||
|
<pre>
|
||
|
sm check_interrupts {
|
||
|
decl { unsigned} flags;
|
||
|
pat enable = { sti(); } | {restore_flags(flags);} ;
|
||
|
pat disable = { cli(); };
|
||
|
|
||
|
is_enabled: disable ==> is_disabled | enable ==> { err("double
|
||
|
enable")};
|
||
|
...
|
||
|
</pre>
|
||
|
A more complete version found 82 errors in the Linux 2.3.99 kernel.
|
||
|
|
||
|
<p>Common mistake:
|
||
|
<pre>
|
||
|
get_free_buffer ( ... ) {
|
||
|
....
|
||
|
save_flags (flags);
|
||
|
cli ();
|
||
|
if ((bh = sh->buffer_pool) == NULL)
|
||
|
return NULL;
|
||
|
....
|
||
|
}
|
||
|
</pre>
|
||
|
<p>(Figure 2 also lists a simple metarule.)
|
||
|
|
||
|
<p>Some checkers produce false positives, because of limitations of
|
||
|
both static analysis and the checkers, which mostly use local
|
||
|
analysis.
|
||
|
|
||
|
<p>How does the <b>block</b> checker work? The first pass is a rule
|
||
|
that marks functions as potentially blocking. After processing a
|
||
|
function, the checker emits the function's flow graph to a file
|
||
|
(including, annotations and functions called). The second pass takes
|
||
|
the merged flow graph of all function calls, and produces a file with
|
||
|
all functions that have a path in the control-flow-graph to a blocking
|
||
|
function call. For the Linux kernel this results in 3,000 functions
|
||
|
that potentially could call sleep. Yet another checker like
|
||
|
check_interrupts checks if a function calls any of the 3,000 functions
|
||
|
with interrupts disabled. Etc.
|
||
|
|
||
|
<h2>This paper</h2>
|
||
|
|
||
|
<p>Writing rules is painful. First, you have to write them. Second,
|
||
|
how do you decide what to check? Was it easy to enumerate all
|
||
|
conventions for JOS?
|
||
|
|
||
|
<p>Insight: infer programmer "beliefs" from code and cross-check
|
||
|
for contradictions. If <i>cli</i> is always followed by <i>sti</i>,
|
||
|
except in one case, perhaps something is wrong. This simplifies
|
||
|
life because we can write generic checkers instead of checkers
|
||
|
that specifically check for <i>sti</i>, and perhaps we get lucky
|
||
|
and find other temporal ordering conventions.
|
||
|
|
||
|
<p>Do we know which case is wrong? The 999 times or the 1 time that
|
||
|
<i>sti</i> is absent? (No, this method cannot figure what the correct
|
||
|
sequence is but it can flag that something is weird, which in practice
|
||
|
useful.) The method just detects inconsistencies.
|
||
|
|
||
|
<p>Is every inconsistency an error? No, some inconsistency don't
|
||
|
indicate an error. If a call to function <i>f</i> is often followed
|
||
|
by call to function <i>g</i>, does that imply that f should always be
|
||
|
followed by g? (No!)
|
||
|
|
||
|
<p>Solution: MUST beliefs and MAYBE beliefs. MUST beliefs are
|
||
|
invariants that must hold; any inconsistency indicates an error. If a
|
||
|
pointer is dereferences, then the programmer MUST believe that the
|
||
|
pointer is pointing to something that can be dereferenced (i.e., the
|
||
|
pointer is definitely not zero). MUST beliefs can be checked using
|
||
|
"internal inconsistencies".
|
||
|
|
||
|
<p>An aside, can zero pointers pointers be detected during runtime?
|
||
|
(Sure, unmap the page at address zero.) Why is metacompilation still
|
||
|
valuable? (At runtime you will find only the null pointers that your
|
||
|
test code dereferenced; not all possible dereferences of null
|
||
|
pointers.) An even more convincing example for Metacompilation is
|
||
|
tracking user pointers that the kernel dereferences. (Is this a MUST
|
||
|
belief?)
|
||
|
|
||
|
<p>MAYBE beliefs are invariants that are suggested by the code, but
|
||
|
they maybe coincidences. MAYBE beliefs are ranked by statistical
|
||
|
analysis, and perhaps augmented with input about functions names
|
||
|
(e.g., alloc and free are important). Is it computationally feasible
|
||
|
to check every MAYBE belief? Could there be much noise?
|
||
|
|
||
|
<p>What errors won't this approach catch?
|
||
|
|
||
|
<h2>Paper discussion</h2>
|
||
|
|
||
|
<p>This paper is best discussed by studying every code fragment. Most
|
||
|
code fragments are pieces of code from Linux distributions; these
|
||
|
mistakes are real!
|
||
|
|
||
|
<p>Section 3.1. what is the error? how does metacompilation catch
|
||
|
it?
|
||
|
|
||
|
<p>Figure 1. what is the error? is there one?
|
||
|
|
||
|
<p>Code fragments from 6.1. what is the error? how does metacompilation catch
|
||
|
it?
|
||
|
|
||
|
<p>Figure 3. what is the error? how does metacompilation catch
|
||
|
it?
|
||
|
|
||
|
<p>Section 8.3. what is the error? how does metacompilation catch
|
||
|
it?
|
||
|
|
||
|
</body>
|
||
|
|