19-Soft-B3 Security · December 2016
Nivaar worked solution (AI-drafted; not reviewed by a licensed engineer)
National Exams, December 2016 — 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), 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), 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; 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}}$, i.e. $$\text{bytes\_written}(L) = L_{\text{eff}} + 1$$ — an off-by-one in the loop bound itself, on top of whatever the clamp allows through.buf holds 24 bytes, indices 0–23, so any write to index 24 or beyond is out of bounds. For $L \le 23$: $L_{\text{eff}} = L$ (clamp not yet relevant), bytes_written $= L+1 \le 24$ — no overflow. At $L = 24$: $L-1 = 23$, not $>24$, so $L_{\text{eff}} = 24$; bytes_written $= 25$, overflowing by $25-24=1$ byte (index 24).len−1 > 24, i.e. it only clamps once $L > 25$ — NOT once $L \ge 24$, which is what the programmer evidently intended given the comparison against 24. At $L = 25$: $L - 1 = 24$, and $24 > 24$ is false, so len is not clamped at all; $L_{\text{eff}} = 25$. $$\text{bytes\_written}(25) = 25 + 1 = 26$$ $$\text{overflow}(25) = 26 - 24 = \boxed{2 \text{ bytes}}$$ This is the worst case: a 25-character argv[2] slips past the clamp's own off-by-one and produces the largest overflow the program allows.len−1 > 24 instead of len > 24 or, correctly, len > sizeof(buf)-1) and the loop bound (i ≤ len instead of i < len). Depending on stack layout, the 1–2 overflow bytes can corrupt adjacent locals or a saved frame/return value; a slightly different clamp constant relative to a larger declared buffer could allow far more.−1, and using sizeof rather than the unrelated literal 24), and copy strictly fewer than that many bytes, reserving one 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 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 (off-by-one) | i < len, plus explicit buf[len]='\0' |
| Worst-case bytes written | 26 (at len = 25) | 24 |
| Maximum overflow | 2 bytes, 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.