Q: 我使用瞭如下代碼將stdin、stdout、stderr重定向到/dev/null
freopen( "/dev/null", "w", stdout );
freopen( "/dev/null", "w", stderr );
freopen( "/dev/null", "r", stdin );
這樣做正確嗎,是否使用"w+"或者"a"更正確一些。在很多代碼中是這樣完成重定向的:
close( 0 );
close( 1 );
close( 2 );
open( "/dev/null", O_RDWR );
dup( 0 );
dup( 0 );
這兩種方式中哪一種更好、更具可移植性。
A: Andrew Gierth <[email protected]>
第一種方式不是總能達到目的。freopen()並不確保新的文件流描述符一定重用底層原有文件句柄。假如未能重用,向stderr流輸出的標準I/O函數最終輸出到/dev/null,但那些向STDERR_FILENO句柄輸出的標準I/O函數就沒這麼幸運了,可能輸出到一些不可預期的文件中去。換句話說,2號句柄此時不再是標準錯誤輸出了。比如:
write( 2, ... )
這樣的調用存在安全問題。第二種方式可以避免上述問題,然而存在競爭環境問題。
現在看下述代碼:
int fd = open( "/dev/null", O_RDWR );
/*
* handle failure of open() somehow
*/
dup2( fd, 0 );
dup2( fd, 1 );
dup2( fd, 2 );
if ( fd > 2 )
{
close( fd );
}
與第二種方式相比,這種代碼是線程安全的。
有人認爲對於後臺守護進程做此類重定向操作浪費資源,建議直接關閉0、1、2號句柄拉倒,這是非常不正確的。假設它們確實被關閉了,則一些普通數據文件句柄將等於0、1、2。以2號句柄爲例,某些庫函數失敗後會向2號句柄輸出錯誤信息,這將破壞原有數據。
D: 小四 <[email protected]> 2002-04-25 16:47
2號句柄的此類安全問題在2002年4月23日得到了實際印證,可參看<<x86/FreeBSD 4.5-RELEASE IO Smash及S/Key機制分析>>。
1987年,Henry Spencer在setuid(7)手冊頁中做了如下建議,一切標準I/O句柄都可能因關閉過而不再是真實的標準I/O句柄,在使用printf()一類的函數前,務必確認這些句柄是期待中的標準I/O句柄。1991年,在comp news上有人重貼了這份文檔。
內核補丁應該確保對於SUID、SGID進程而言,0、1、2號句柄不會被打開後指向一個普通文件。這有很多實現方式,比如使它們全部指向/dev/null。這種限制不應該在庫函數一級實現,可能有些SUID、SGID程序直接使用系統調用。
stdin、stdout、stderr中某一個被關閉,都可能潛在存在問題。
1992年W. Richard Stevens在<<Advanced Programming in the UNIX Environment>>中建議Daemon進程應該關閉所有不必要的文件句柄,並將stdin、stdout、stderr指向/dev/null。
自1998年以來,OpenBSD內核中execve()裏有一個檢查,如果句柄0、1、2是關閉的,就打開/dev/null,使之對應0、1、2號句柄。這樣就可以安全地執行setuid程序了。FreeBSD/NetBSD直至最近纔再次暴露出類似問題,而Linux在glibc中做了一些檢查。
但是,OpenBSD這個檢查存在一個問題,當falloc()失敗時,應該轉向錯誤處理,而
不是簡單地跳出循環。art在註釋中指出了這點,卻無人去修正它。
--------------------------------------------------------------------------
sys/kern/kern_exec.c 在一個循環中,內核試圖打開/dev/null,使之對應0-2號句柄
(...)
if ( ( error = falloc( p, &fp, &indx ) ) != 0 )
{
break;
}
(...)
--------------------------------------------------------------------------於是本地用戶獲得一個內核文件表項相關的競爭環境,可以獲取root權限。