These problems should be done on your own. We're not going to be grading them strictly (we'll mainly look at whether you attempted them). But they will be reinforcing knowledge and skills, so you should totally work through them carefully.
Crash recovery
Your friend wants to build a file system that tolerates crashes. Your friend proposes write-behind journaling. In this proposal, there is a journal, but the file system writes to the journal only after checkpointing (“checkpointing”, recall, means applying an operation to the on-disk data structures). Specifically, (1) the file system writes a TxnEnd record for a given transaction only after the TxnBegin record and all journal entries for the given transaction are written, and (2) the file system writes individual journal entries only after checkpointing the operation described by that entry. The recovery protocol looks for incomplete operations (those that are part of a transaction that lacks a TxnEnd record) and undoes those operations, similar to the way that recovery works for undo-only logging.
Assume that a crash can happen at any time. Does your friend’s proposal work? If so, argue that it is correct. If not, explain why not. Use no more than four sentences.
Buffer overflow vulnerabilities
For the statements below, please state whether they are true or false. Justify each answer.
- "If a server has a buffer overflow vulnerability, that means the server definitely has a bug."
- "Buffer overflow vulnerabilities can be ruled out by making the stack non-executable."
- "Buffer overflow vulnerabilities can be ruled out by making program text read-only."
- "Buffer overflow vulnerabilities can be ruled out with the W XOR X security policy."
- "Buffer overflow vulnerabilities can be eliminated with ASLR (address space layout randomization)."
- "Buffer overflow vulnerabilities are not possible on a 64-bit architecture."
- "If a buffer overflow vulnerability is exploited, this implies that the attacker has changed
%cr3
."
Setuid
Assume that a multiuser Unix system has a buggy implementation of passwd
. As usual, passwd
is a setuid executable that is owned by root, world executable, and world readable; thus, any user on the system can invoke passwd
, and when they do, the resulting process runs with root’s privileges. The aforementioned bug allows any invoker of passwd
to mount a buffer overflow attack, and thereby gain a root shell. Assume that this version of passwd
is the only buggy program on the system (that’s wildly unrealistic, but it will keep the question focused).
An adversarial user on the system knows about the vulnerability in passwd
. This user figures that eventually the root user (as system administrator) will replace passwd
with a non-buggy version. But the adversarial user wants to be able to invoke the buggy version of passwd
in the future to gain root access. So the adversarial user takes some action A.
Later, the root user indeed replaces passwd
with a non-buggy version:
# rm /sbin/passwd
# apt source passwd // gets the source for the updated passwd
...
# make install // replaces /sbin/passwd with a non-buggy version
Yet, some time after root does the above, the adversarial user somehow gets a root shell.
What was action A? Note that it was not a version of the trusting trust attack, because that presupposes a bugged compiler, nor was it exploiting any issue in apt
, since that presupposes apt
is buggy, both in contradiction to the assumption highlighted above. Also, the attacker didn’t copy passwd
; invoking the copy would not have been sufficient to gain a root shell.
Explain why action A resulted in the attacker being able to get a root shell.
Concurrency Review
Consider the following implementation of a spinlock:
struct Lock {
int locked;
}
int exchange_value(int* ptr, int val) {
int was;
was = *ptr;
*ptr = val;
return was;
}
void acquire(Lock *lock) {
pushcli(); /* disable interrupts */
while (1) {
if (exchange_value(&lock->locked, 1) == 0)
break;
}
}
void release(Lock *lock){
exchange_value(&lock->locked, 0);
popcli(); /* re-enable interrupts */
}
Assume that the machine provides sequential consistency. The functions pushcli()
and popcli()
disable and re-enable interrupts, respectively.
Is the code correct? If the code is correct, explain what invariant is maintained. If the code is not correct, give a problematic interleaving and explain why it is problematic.
Monitors Review
In this problem, you will implement a monitor to help a set of drivers (modeled as threads) synchronize access to a set of five keys and a set of ten cars. Here is the problem setup:
- The relationship between the keys and the cars is that key 0 operates cars 0 and 1, key 1 operates cars 2 and 3, etc. That is, key i works for cars 2i and 2i+1.
- If a key is being used to operate one car, it cannot be used to operate the other.
- A driver requests a particular car (which implies that the driver needs a particular key). However, there may be many more drivers than cars. If a driver wants to go driving but cannot get its desired car or that car's key, it waits until the car and key become available. When a driver finishes driving, it returns its key and notifies any drivers waiting for that key that it is now free.
- You must allow multiple drivers to be out driving at once, and you must not have busy waiting or spin loops.
- We repeat: there could be many, many instances of
driver()
running, each of which you can assume is in its own thread, and all of which use the same monitor,mon
.
Below, fill in the monitor's remaining variable(s) and implement the monitor's take_key()
and return_key()
methods. Follow the coding standards given in class.
typedef enum {FREE, IN_USE} key_status;
class Monitor {
public:
Monitor() { memset(&keys, FREE, sizeof(key_status)*5); }
~Monitor() {}
void take_key(int desired_car);
void return_key(int desired_car);
private:
Mutex mutex;
key_status keys[5];
/* ADD MATERIAL BELOW THIS LINE */
};
void driver(thread_id tid, Monitor* mon, int desired_car) {
/* you should not modify this function */
mon->take_key(desired_car);
drive();
mon->return_key(desired_car);
}
void Monitor::take_key(int desired_car) {
/* FILL IN THIS FUNCTION. Note that the argument refers
to the desired car. */
}
void Monitor::return_key(int desired_car) {
/* FILL IN THIS FUNCTION. Note that the argument refers
to the desired car. */
}
Handing in the homework
Use Gradescope; you can enroll in our course with entry code 4J462V.