+수동 테스트 코드
def my_sqrt(x):
"""Computes the square root of x, using the Newton-Raphson method"""
approx = None
guess = x / 2
while approx != guess:
approx = guess
guess = (approx + x / approx) / 2
return approx
+수동 테스트의 단점: 수동으로 매우 제한된 수의 실행과 그 결과 만 확인하며, 프로그램을 변경 한 후에는 테스트 프로세스를 반복해야 한다.=>테스트 자동화가 유용함.
result = my_sqrt(4)
expected_result = 2.0
if result == expected_result:
print("Test passed")
else:
print("Test failed")입력하세요
Test passed
def assertEquals(x, y, epsilon=1e-8):
assert abs(x - y) < epsilon
assertEquals(my_sqrt(4), 2)
assertEquals(my_sqrt(9), 3)
assertEquals(my_sqrt(100), 10)
+√xX√x=x 역시 이상 없으며 수천 개의 값을 빠른 속도로 쉽게 테스트 가능함.
:런타임 검사의 중요한 제한 사항은 검사할 결과가 있는 경우에만 정확성을 보장한다. 즉, 항상 결과가 있을 것이라고 보장하지는 않는다.
def sqrt_program(arg: str) -> None:
x = int(arg)
print('The root of', x, 'is', my_sqrt(x))
sqrt_program("4")
The root of 4 is 2.0.
아무 문제 없어 보이지만 sqrt_program(-1) 호출 시에는? 무한 루프에 빠짐
from ExpectError import ExpectTimeout
with ExpectTimeout(1):
sqrt_program("-1")
=>위와 같은 구조를 이용해서 1초 후 실행 중단시킴
:오류 메시지
def sqrt_program(arg: str) -> None:
x = int(arg)
if x < 0:
print("Illegal Input")
else:
print('The root of', x, 'is', my_sqrt(x))
sqrt_program("-1")
Illegal Input
그렇다면 문자열이라면?
from ExpectError import ExpectError
with ExpectError():
sqrt_program("xyzzy")
:오류 메시지
def sqrt_program(arg: str) -> None:
try:
x = float(arg)
except ValueError:
print("Illegal Input")
else:
if x < 0:
print("Illegal Number")
else:
print('The root of', x, 'is', my_sqrt(x))
with ExpectError():
root = my_sqrt(0)
:오류 메시지
+https://www.fuzzingbook.org/html/Importing.html
(fuzzer 사용법 참고)
: Fuzzer는 RandomFuzzer와 함께 fuzzers의 기본 클래스이다. fuzz()는 Fuzzer 객체의 메소드이며 도출된 문자열을 반환해준다. 퍼징의 본질은 "Create random inputs, and see if they break things. Just let it run long enough, and you'll see."이다.
ex)
random_fuzzer = RandomFuzzer()
random_fuzzer.fuzz()
print(RandomFuzzer.__init__.__doc__)
Produce strings of `min_length` to `max_length` characters
in the range [`char_start`, `char_start` + `char_range`)
: Fuzzer는 Runner와 쌍을 이룰 수 있다. Runners는 클래스의 상태와 결과(PASS, FAIL, or UNRESOLVED)를 출력해준다.
: 무작위 문자열 생성 뒤 버퍼 문자 변수에 더해준 뒤 string으로 반환하는 방식을 이용해 fuzz generator을 만든다.
-random.randrange(start, end) – return a random number 난수 반환 [start, end )
-range(start, end) – create an iterator (which can be used as a list) with integers in the range [start, end )
-for elem in list: body – execute body in a loop with elem taking each value from list.
-for i in range(start, end): body – execute body in a loop with i from start to end −1.
-chr(n) – return a character with ASCII code n
import random
def fuzzer(max_length: int = 100, char_start: int = 32, char_range: int = 32) -> str:
"""A string of up to `max_length` characters
in the range [`char_start`, `char_start` + `char_range`)"""
string_length = random.randrange(0, max_length + 1)
out = ""
for i in range(0, string_length):
out += chr(random.randrange(char_start, char_start + char_range))
return out
fuzzer()
=>랜덤 문자열 반환
+ord(c)를 통해 아스키코드 값 이용해서 범위 생성 방법도 있음.
: fuzzed 된 입력이 있는 외부 프로그램 호출
import os
import tempfile
basename = "input.txt"
tempdir = tempfile.mkdtemp()
FILE = os.path.join(tempdir, basename)
print(FILE) #파일 생성
data = fuzzer()
with open(FILE, "w") as f:
f.write(data)
#파일에 작성
contents = open(FILE).read()
print(contents)
assert(contents == data)
#파일에 작성 후 확인
import os
import subprocess
program = "bc"
with open(FILE, "w") as f:
f.write("2 + 2\n")
result = subprocess.run([program, FILE],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
=> 결과 확인, 산술 연산의 결과
=> 상태 확인(0이면 정산 반환값)
=> 오류 메시지
: 입력의 최대 길이(미리 정해진) 등을 넘었을 시에 버퍼오버플로우.
def crash_if_too_long(s):
buffer = "Thursday"
if len(s) > len(buffer):
raise ValueError
from ExpectError import ExpectError
trials = 100
with ExpectError():
for i in range(trials):
s = fuzzer()
crash_if_too_long(s)
: 오류 메시지
:여러 프로그래밍 언어에서는 예외적인 상황에서 특수한 오류 코드를 반환한다. ex) C언어의 getchar()=>EOF(end of file) 오류
def hang_if_no_space(s):
i = 0
while True:
if i < len(s):
if s[i] == ' ':
break
i += 1
from ExpectError import ExpectTimeout
trials = 100
with ExpectTimeout(2):
for i in range(trials):
s = fuzzer()
hang_if_no_space(s)
=>시간 초과 메커니즘을 사용하여 잠시 후 이 기능을 중단한다. 코드 실행 중단 및 오류 메시지 출력
: 크기가 프로그램 메모리 넘어갈 만큼 크거나 문자열의 수보다 작을 때 등등 문제 발생. (generate uncommon values in the input)
def collapse_if_too_large(s):
if int(s) > 1000:
raise ValueError
long_number = fuzzer(100, ord('0'), 10)
print(long_number)
with ExpectError():
collapse_if_too_large(long_number)
: 에러 메시지
C 언어를 통한 간단한 예시
#include <stdlib.h>
#include <string.h>
int main(int argc, char** argv) {
/* Create an array with 100 bytes, initialized with 42 */
char *buf = malloc(100);
memset(buf, 42, 100);
/* Read the N-th element, with N being the first command-line argument */
int index = atoi(argv[1]);
char val = buf[index];
/* Clean up memory so we don't leak */
free(buf);
return val;
}
!./program 110
buf[110]에 접근하려면 AdressSanitizer의 범위를 넘어간 에러 발생
: 정보 노출은 유효한 메모리에서도 충분히 일어날 수 있음.
secrets = ("<space for reply>" + fuzzer(100) +
"<secret-certificate>" + fuzzer(100) +
"<secret-key>" + fuzzer(100) + "<other-secrets>")
=>실제 데이터와 임의의 데이터로 만들어진 프로그램 메모리
uninitialized_memory_marker = "deadbeef"
while len(secrets) < 2048:
secrets += uninitialized_memory_marker
def heartbeat(reply: str, length: int, memory: str) -> str:
# Store reply in memory
memory = reply + memory[len(reply):]
# Send back heartbeat
s = ""
for i in range(length):
s += memory[i]
return s
heartbeat("hat", 500, memory=secrets)
이러한 문제 감지 코드
from ExpectError import ExpectError
with ExpectError():
for i in range(10):
s = heartbeat(fuzzer(), random.randint(1, 500), memory=secrets)
assert not s.find(uninitialized_memory_marker)
assert not s.find("secret")
: 오류 메시지
import string
def no_backslash_d(inp):
pattern = "\\D"
index = inp.find(pattern)
if index < 0 or index + len(pattern) >= len(inp):
return True
c = inp[index + len(pattern)]
assert c in string.printable
with ExpectError():
no_backslash_d("\\D\0")
def no_8bit(inp):
for i in range(len(inp) - 1):
assert ord(inp[i]) <= 127 or inp[i + 1] != '\n'
return True
with ExpectError():
no_8bit("ä\n")
def no_dot(inp):
assert inp != ".\n"
return True
real_troff_runner = BinaryProgramRunner("troff")
for i in range(100):
result, outcome = random_fuzzer.run(real_troff_runner)
if outcome == Runner.FAIL:
print(result)