現在開發的項目是從solaris到linux的應用移植。經常用到popen函數,使用8192字節的數組讀取popen輸出,但沒有進行溢出判斷。
剛開始認爲是一個簡單的內存越界,但對popen和PIPE調查以後,疑惑越來越多了。
1)問題的引出
popen使用管道來記錄被調用命令的輸出,那麼popen的最大寫入字節數必然是管道的最大值。
使用linux的ulimit -a來查看系統限制:
[syscom@sysbase0-0 linux]$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 16204
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 1024
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
查看solaris的系統限制:
bash-3.00$ ulimit -a
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
file size (blocks, -f) unlimited
open files (-n) 256
pipe size (512 bytes, -p) 10
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 29995
virtual memory (kbytes, -v) unlimited
可以看到在linux系統上pipe size 爲512bytes * 8= 4096bytes。solaris系統上pipe size爲512bytes * 10= 5120bytes。
無論是4096還是5120都是遠遠小於8912的。因此使用8912字節的buf來讀取popen的輸出時絕對不會出現內存越界問題了。
2)問題的深入
通過ulimit看似得到了正確的結果,但在實際測試中卻讓人大跌眼鏡!
測試程序:
test_popen.c
#include<stdio.h>
int main()
{
FILE *fp;
int i;
char *p;
char buf[128];
fp = popen("./test_print", "r");
if(fp ==NULL) {
printf("NG\n");
return -1;
}
fgets(buf, 128, fp);
pclose(fp);
return 0;
}
test_print.c
#include <stdio.h>
int main()
{
unsigned int i;
for(i=0; i<0xffffffff;i++)
printf("a");
return 0;
}
將test_popen.c 和test_print.c分別編譯爲test_popen和test_print,運行test_popen,程序竟然正常運行!test_print明明輸出了4G的字符啊
3)探究原理。
通過man 7 pipe來理解PIPE(我的man版本是1.6f)
PIPE_BUF
POSIX.1-2001 says that write(2)s of less than PIPE_BUF bytes must be
atomic: the output data is written to the pipe as a contiguous
sequence. Writes of more than PIPE_BUF bytes may be non-atomic: the
kernel may interleave the data with data written by other processes.
POSIX.1-2001 requires PIPE_BUF to be at least 512 bytes. (On Linux,
PIPE_BUF is 4096 bytes.) The precise semantics depend on whether the
file descriptor is non-blocking (O_NONBLOCK), whether there are multi-
ple writers to the pipe, and on n, the number of bytes to be written:
PIPE_BUF確實爲4096,但PIPE_BUF是指原子寫的最大值,4kb剛好是1page size,並不是指PIPE SIZE
在谷歌上搜索內核源碼,在3.10的內核中有如下:
133 /* Differs from PIPE_BUF in that PIPE_SIZE is the length of the actual
134 memory allocation, whereas PIPE_BUF makes atomicity guarantees. */
135 #define PIPE_SIZE PAGE_SIZE
明確說明了PIPE_BUF和PIPE_SIZE的不同
進一步調查,發現在2.6.11之前,PIPE_SIZE爲4k,之後內核中PIPE_SIZE爲64k,那爲何能寫入多達4G的字符呢?
這時我想到了popen的源碼,查看了popen在BSD的實現,除了使用pipe函數外,與system調用並無區別。
點我查看popen源代碼
4)結論
依賴linux的pipe特性的程序並不是好的設計,非常容易出亂子(目前還未發生),最好還是老老實實地做內存越界判斷,降低與系統的耦合性。
歡迎轉載,轉載請註明出處和鏈接
by judwenwen2009