개발자의 실수 - Type Error
https://dreamhack.io/lecture/courses/118
Logical Bug: Type Error
타입을 잘못 사용하여 발생할 수 있는 버그를 학습합니다.
dreamhack.io
Type Error란?
Type Error는 자료형 선언할 때 크기, 용도, 부호 여부를 고려하지 않고 사용할 때 발생하는 버그이다.
자료형-크기/범위/용도
(signed) char | 1 바이트 | [−128, +127] | 정수, 문자 |
unsigned char | [0, 255] | ||
(signed) short (int) | 2 바이트 | [−32,768, +32,767] | 정수 |
unsigned short (int) | [0, 65,535] | ||
(signed) int | 4 바이트 | [−2,147,483,648, +2,147,483,647] | 정수 |
unsigned int | [0, 4,294,967,295] | ||
size_t | 32bit: 4 바이트 64bit: 8 바이트 |
생략 | 부호 없는 정수 |
(signed) long | 32bit: 4 바이트 64bit: 8 바이트 |
정수 | |
unsigned long | |||
(signed) long long | 32bit: 8 바이트 64bit: 8 바이트 |
정수 | |
unsigned long long | |||
float | 4 바이트 | 실수 | |
double | 8 바이트 | 실수 | |
Type * | 32bit: 4 바이트 64bit: 8 바이트 |
주소 |
Out of Range : 데이터 유실
// Name: out_of_range.c
// Compile: gcc -o out_of_range out_of_range.c
#include <stdio.h>
unsigned long long factorial(unsigned int n) {
unsigned long long res = 1;
for (int i = 1; i <= n; i++) {
res *= i;
}
return res;
}
int main() {
unsigned int n;
unsigned int res;
printf("Input integer n: ");
scanf("%d", &n);
if (n >= 50) {
fprintf(stderr, "Input is too large");
return -1;
}
res = factorial(n);
printf("Factorial of N: %u\n", res);
}
위와 같은 예제에서 16, 17을 입력하다가 갑자기 18에서 17보다 적어지는 것을 확인할 수 있다. 여기서 왜 작아지는 지를 생각해보면 unsigned int 형의 크기를 생각 할 수 있는데, unsigned int의 크기는 4바이트이다. 그런데 !18 = 0x16BEEC CA730000 이다 그런데 4바이트 이므로 상위만 옮겨지고 하위 4바이트를 살리면, 0xCA730000이 된다. 이는 출력에서 본 3,396,534,272와 똑같다. 이처럼 그 변수를 저장할 수 있는 범위를 넘어가게 되면 저장할 수 있는 만큼만 남고 나머지는 다 버려지게 된다.
// Name: oor_signflip.c
// Compile: gcc -o oor_signflip oor_signflip.c
#include <stdio.h>
unsigned long long factorial(unsigned int n) {
unsigned long long res = 1;
for (int i = 1; i <= n; i++) {
res *= i;
}
return res;
}
int main() {
int n;
unsigned int res;
printf("Input integer n: ");
scanf("%d", &n);
if (n >= 50) {
fprintf(stderr, "Input is too large");
return -1;
}
res = factorial(n);
printf("Factorial of N: %u\n", res);
}
이 코드는 앞에 코드와는 다르게 변수 n의 자료형이 int 형으로 변환 된것을 확인할 수 있다. 이는 입력 값이 음수가 될 수 있다는 뜻이다. 그런데 -1을 입력하면 n을 저장하는 메모리 공간에서는 0xfffffffff 이렇게 저장된다. 왜 그런지는 2의 보수를 공부하면 알 수 있다. (난 앎)
그런데 factorial 함수는 unsigned int형으로 받으므로, 이 값은 부호 없는 정수인 4,294,967,295로 전달되고, 결국 4,294,967,295번 반복문을 실행하게 된다. 당연히 시간이 오래 걸릴 뿐만 아니라 전과 같이 쟤대로 된 값이 나오지도 않는다.
이러한 문제를 예방하려면 자료형을 알맞게 써야된다.
// Name: oor_bof.c
// Compile: gcc -o oor_bof oor_bof.c -m32
#include <stdio.h>
#define BUF_SIZE 32
int main() {
char buf[BUF_SIZE];
int size;
printf("Input length: ");
scanf("%d", &size);
if (size > BUF_SIZE) {
fprintf(stderr, "Buffer Overflow Detected");
return -1;
}
read(0, buf, size);
return 0;
}
위와 같은 방법을 이용해 이러한 코드가 있을 때 -1을 입력해주면 if문에 걸리지 않고, read에서 오버플로우를 이르킬 수 있다.
타입 오버플로우랑 타입 언더 플로우는 그냥 int형 최대에서 1을 더해줄 때는 오버플로우가 나서 음수의 최대치로 가는 것과 같은 것을 말한다
예시
unsigned int a = 4,294,967,295 + 1
printf("%d", a)
-> 0
// Name: integer_overflow.c
// Compile: gcc -o integer_overflow integer_overflow.c -m32
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
unsigned int size;
scanf("%u", &size);
char *buf = (char *)malloc(size + 1);
unsigned int read_size = read(0, buf, size);
buf[read_size] = 0;
return 0;
}
위와 같은 상황에서 unsigned int의 최댓값인 4,294,967,295을 입력하면 overflow로 인해 size + 1의 값은 0이 된다. 한마디로 char buf[0] 이 되는 것이다. 그렇지만 size는 그대로 4,294,967,295이기 때문에 read로 읽게 될 경우 4,294,967,295의 값 만큼 쓸 수 있는 heap overflow가 발생하게 된다.