19-Soft-B3 Security · May 2014
Nivaar worked solution (AI-drafted; not reviewed by a licensed engineer)
National Exams, May 2014 — 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, Block Ciphers), Ch. 21 (Public-Key Infrastructure, Certificate Authorities), 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); Anderson, Security Engineering, 3rd ed., Ch. 4 (Access Control, Least Privilege), Ch. 1 & 9 (Defense in Depth, Separation of Duty).
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[16];
5:
6: len = strlen(arg);
7:
8: if (len > 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 16-byte stack array (valid indices 0–15); the length check on line 8–9 clamps len to at most 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 how many bytes past the end of buf an attacker can write; (b) a corrected version that removes the vulnerability.
Approach. Trace the bound-check and loop arithmetic against buf's actual declared capacity (not the unrelated constant 24) to find the worst-case number of out-of-bounds bytes written, then re-derive the same bound from sizeof(buf) to fix it.
char buf[16] has exactly 16 valid byte positions, indices 0 through 15. Any write to index 16 or higher is outside the array.argv[2] with strlen(arg) ≥ 24, line 8–9 clamps len to exactly 24. This bound of 24 is unrelated to buf's real size of 16 — it is already 8 bytes too permissive before the loop is even considered.for (i = 0; i <= len; i++) executes for $i = 0, 1, \dots, len$, i.e. $len+1$ iterations — an off-by-one, since copying "len characters" should use i < len. With the clamped worst case $len = 24$, the loop writes indices $i = 0 \ldots 24$: $$\text{bytes\_written} = len + 1 = 24 + 1 = 25$$buf on the stack, fully populated from attacker-controlled bytes of argv[2] — a classic stack buffer overflow (CWE-121/CWE-193, off-by-one combined with a bound check against the wrong constant). Depending on the compiler's stack layout, those 9 bytes can corrupt adjacent local variables, a saved frame pointer, or (with a slightly larger overflow) the function's return address, which is the basis of classic stack-smashing exploits.if (len > sizeof(buf) - 1) len = sizeof(buf) - 1; — giving $len_{max} = 16 - 1 = 15$, reserving one byte for a NUL terminator.len bytes (drop the ≤ off-by-one) and null-terminate explicitly: for (i = 0; i < len; i++) buf[i] = arg[i]; buf[len] = '\0';. The corrected worst case writes indices $0 \ldots 14$ plus a terminator at index 15: $$\text{bytes\_written}_{fixed} = len_{max} + 1 = 15 + 1 = 16 = \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 | 16 bytes | 16 bytes |
| Length clamp | len ≤ 24 (wrong constant) | len ≤ sizeof(buf)-1 = 15 |
| Loop bound | i ≤ len (off-by-one) | i < len, plus explicit buf[len]='\0' |
| Worst-case bytes written | 25 | 16 |
Overflow past buf | 9 bytes | 0 bytes |
Two secondary observations worth noting, though they are not the primary vulnerability: strncpy(string, argv[1], 12) in main is itself bound-safe (it will never write past string's 12 bytes), but if argv[1] is 12 characters or longer, strncpy does not append a NUL terminator, leaving string non-terminated for any later use; and neither argv[1] nor argv[2] is checked against argc before use, so a caller supplying too few arguments causes main to read/pass a NULL or out-of-bounds pointer. Both are latent defects in the same defensive-programming spirit as the main fix, even though the exam's Part a)/b) is centred on foo's overflow.