19-Soft-B3 Security · December 2018
Nivaar worked solution (AI-drafted; not reviewed by a licensed engineer)
National Exams, December 2018 — 04-Soft-B3, Security/Safety (closed book, 3 hours, no calculator). FIVE of the seven questions constitute a complete paper (the first five as answered in the answer book are marked, each of equal value); this solution answers all seven as a full study resource. Most questions call for essay-format answers; clarity and organisation of the answer are important. Question 6 asks for a security analysis of a short C program.
Reference texts. Stallings & Brown, Computer Security: Principles and Practice, 4th ed., Ch. 2–3 (Cryptographic Tools, One-Time Pad, Stream/Block Ciphers, Modes of Operation), Ch. 21 (Public-Key Infrastructure, Certificate Authorities), Ch. 10 (Key Management, Diffie–Hellman, RSA), Ch. 3 (Hash Functions, MAC), Ch. 23 (Digital Signatures), Ch. 3 & 24 (User Authentication, Two-Factor, SSO, Password Storage), Ch. 9 (Firewalls, DMZ), Ch. 8 (Intrusion Detection, Honeypots), Ch. 10 (Buffer Overflow), Ch. 1 (Security Concepts — CIA Triad); Anderson, Security Engineering, 3rd ed., Ch. 4 (Access Control), Ch. 1 (Security Concepts).
Question text not reproduced: the examination questions are © Engineers and Geoscientists BC. Open the official past paper (linked at the top of this page) to read the question, then follow the worked solution below.
Given. The listed C program, with the line numbering from the exam:
1: int foo(char *arg)
2: {
3: int i, len;
4: char buf[24];
5:
6: len = strlen(arg);
7:
8: if (len-1 > 24)
9: len = 24;
10: for (i = 0; i < len; i++)
11: buf[i] = arg[i];
12: return 0;
13: }
14:
15: int main(int argc, char *argv[])
16: {
17: char string[12];
18:
19: strncpy(string, argv[1], 12);
20: foo(argv[2]);
21: return 0;
22: }
buf is a fixed 24-byte stack array (valid indices 0–23); the length clamp on line 8–9 compares len−1 (not len) against 24; the copy loop on line 10–11 runs while i < len (a correctly strict bound, unlike some variants of this question); argv[2], an attacker-controlled command-line argument, is passed into foo with no length check of its own.
Find. (a) Whether the program is vulnerable, and if so the worst-case number of bytes an attacker can write past the end of buf, and at exactly what input length that worst case occurs; (b) a corrected version that removes the vulnerability.
Approach. Trace the clamp condition and the loop's iteration count as an explicit function of strlen(arg), evaluate it at and around the clamp's own boundary (not just "very long input"), and compare against buf's true capacity, sizeof(buf) = 24.
for (i = 0; i < len_eff; i++) writes indices $0 \ldots L_{\text{eff}}-1$, i.e. exactly $$\text{bytes\_written}(L) = L_{\text{eff}}$$ — the loop bound itself is correctly strict here, so the only defect is in the clamp comparison.buf holds 24 bytes, indices 0–23, so writing 24 bytes (indices 0–23) exactly fills it and writing 25 or more overflows it. For $L \le 24$: $L - 1 \le 23$, not $>24$, so $L_{\text{eff}} = L \le 24$ — bytes_written $\le 24$, no overflow (at $L=24$ the buffer is exactly filled, zero slack, but not exceeded).len−1 > 24, i.e. it only clamps once $L > 25$ — NOT once $L \ge 24$, which is what a correct guard against a 24-byte buffer requires. At $L = 25$: $L - 1 = 24$, and $24 > 24$ is false, so len is not clamped at all; $L_{\text{eff}} = 25$. $$\text{overflow}(25) = 25 - 24 = \boxed{1 \text{ byte}}$$ A 25-character argv[2] slips past the clamp's own off-by-one and writes one byte (index 24) beyond the end of buf.len−1 > 24 instead of len > 24, or correctly, len > sizeof(buf)-1). It is a narrow, one-byte overflow reachable only by a single exact input length, but it is still attacker-controlled memory corruption of whatever stack data sits immediately after buf — commonly a saved register, another local variable, or (depending on compiler layout and stack protections) the frame's canary/return address.−1, and using sizeof rather than the unrelated literal 24), and copy strictly fewer than that many bytes, reserving one slot for a NUL terminator: if (len > sizeof(buf) - 1) len = sizeof(buf) - 1; then for (i = 0; i < len; i++) buf[i] = arg[i]; buf[len] = '\0';. With $\text{sizeof(buf)} = 24$, this gives $L_{max} = 23$, and $$\text{bytes\_written}_{fixed} = L_{max} + 1 = 23 + 1 = 24 = \text{sizeof(buf)}$$ exactly filling buf (data plus terminator) and never exceeding it, for zero bytes of overflow regardless of how long arg is.| Quantity | Vulnerable version | Corrected version |
|---|---|---|
buf capacity | 24 bytes | 24 bytes |
| Clamp test | len-1 > 24 (wrong, triggers only at len>25) | len > sizeof(buf)-1 = 23 |
| Loop bound | i < len (correctly strict) | i < len, plus explicit buf[len]='\0' |
| Worst-case bytes written | 25 (at len = 25) | 24 |
| Maximum overflow | 1 byte, exactly at len = 25 | 0 bytes, any len |
Two secondary observations, not the primary vulnerability: strncpy(string, argv[1], 12) in main never writes past string's 12 bytes, but if argv[1] is 12 characters or longer, strncpy does not NUL-terminate it, leaving string non-terminated for any later use; and neither argv[1] nor argv[2] is checked against argc before use, so too few command-line arguments causes main to dereference a NULL/out-of-bounds pointer.