整数に関係するバグについてのメモ。
環境
Ubuntu 14.04.4 LTS 64bit版
$ uname -a Linux vm-ubuntu64 3.19.0-25-generic #26~14.04.1-Ubuntu SMP Fri Jul 24 21:16:20 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 14.04.4 LTS Release: 14.04 Codename: trusty $ gcc --version gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
整数オーバーフロー(Integer Overflow)
整数オーバーフローは、演算の結果、整数が表現できる最大値を越えてしまうことにより発生するバグである。
/* integer_overflow.c */ #include <stdio.h> #include <stdlib.h> int main() { char buf[40]; int n; scanf("%d", &n); printf("n+1 == %d\n", n+1); if (n+1 > sizeof(buf)) { puts("too long!"); exit(1); } read(0, buf, n); return 0; }
上のコードではバッファサイズのチェックが行われているが、次のように4294967295(=232-1)を与えたときチェックを通過してしまう。
$ gcc integer_overflow.c $ ./a.out 10 n+1 == 11 AAAA $ ./a.out 80 n+1 == 81 too long! $ python Python 2.7.6 (default, Jun 22 2015, 17:58:13) [GCC 4.8.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> (1<<32)-1 4294967295 >>> $ ./a.out 4294967295 n+1 == 0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA *** stack smashing detected ***: ./a.out terminated Aborted (core dumped)
これを防ぐには、入力に依存しない箇所で計算を行うようにすればよい。
--- integer_overflow.c 2016-09-27 15:23:56.920312000 +0900 +++ patched/integer_overflow.c 2016-09-27 15:24:30.728312000 +0900 @@ -9,7 +9,7 @@ scanf("%d", &n); printf("n+1 == %d\n", n+1); - if (n+1 > sizeof(buf)) { + if (n > sizeof(buf)-1) { puts("too long!"); exit(1); }
符号エラー(Signedness Error)
符号エラーは、符号付き整数が符号なし整数として扱われる、あるいはその逆が起こることにより発生するバグである。
/* signedness_error.c */ #include <stdio.h> #include <stdlib.h> int main() { char buf[40]; int n; scanf("%d", &n); if (n > 40) { puts("too long!"); exit(1); } read(0, buf, n); return 0; }
上のコードではバッファサイズのチェックが行われているが、次のように-1を与えたときチェックを通過してしまう。
$ gcc signedness_error.c $ ./a.out 10 AAAA $ ./a.out 80 too long! $ ./a.out -1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA *** stack smashing detected ***: ./a.out terminated Aborted (core dumped) $ strace ./a.out execve("./a.out", ["./a.out"], [/* 21 vars */]) = 0 brk(0) = 0x1178000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5d72941000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=37010, ...}) = 0 mmap(NULL, 37010, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f5d72937000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P \2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=1840928, ...}) = 0 mmap(NULL, 3949248, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5d7235c000 mprotect(0x7f5d72516000, 2097152, PROT_NONE) = 0 mmap(0x7f5d72716000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ba000) = 0x7f5d72716000 mmap(0x7f5d7271c000, 17088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f5d7271c000 close(3) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5d72936000 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5d72934000 arch_prctl(ARCH_SET_FS, 0x7f5d72934740) = 0 mprotect(0x7f5d72716000, 16384, PROT_READ) = 0 mprotect(0x600000, 4096, PROT_READ) = 0 mprotect(0x7f5d72943000, 4096, PROT_READ) = 0 munmap(0x7f5d72937000, 37010) = 0 fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5d72940000 read(0, -1 "-1\n", 1024) = 3 read(0, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 4294967295) = 49 open("/dev/tty", O_RDWR|O_NOCTTY|O_NONBLOCK) = 3 writev(3, [{"*** ", 4}, {"stack smashing detected", 23}, {" ***: ", 6}, {"./a.out", 7}, {" terminated\n", 12}], 5*** stack smashing detected ***: ./a.out terminated ) = 52 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5d7293f000 rt_sigprocmask(SIG_UNBLOCK, [ABRT], NULL, 8) = 0 gettid() = 4558 tgkill(4558, 4558, SIGABRT) = 0 --- SIGABRT {si_signo=SIGABRT, si_code=SI_TKILL, si_pid=4558, si_uid=1000} --- +++ killed by SIGABRT (core dumped) +++ Aborted (core dumped)
straceコマンドの結果から、チェックを通過した-1がread関数では符号なし整数4294967295として扱われていることがわかる。
これを防ぐには、最初から符号なし整数として宣言すればよい。
--- signedness_error.c 2016-09-27 15:50:22.524312000 +0900 +++ patched/signedness_error.c 2016-09-27 15:50:51.888312000 +0900 @@ -5,7 +5,7 @@ int main() { char buf[40]; - int n; + unsigned int n; scanf("%d", &n); if (n > 40) {