為什麼在Docker裡使用gdb偵錯程式會報錯

marshalzxy發表於2018-12-03

背景

前幾天一個小夥伴發郵件問我,他在docker內部使用gdb除錯時刻遇到了gdb如下報錯資訊
ptrace:Operation not permitted
當時我的答覆是在docker create或者docker run時刻開啟萬精油–privileged引數。小夥伴的問題就此解決了。
但是事實並非如此簡單

Docker上涉及到gdb除錯許可權的特性

capabilities

Docker借用了linux對程式設定capabilities,而其子程式繼承父程式capabilites特性來完成對容器capacities的控制。Docker
create和docker run引數中有下面兩個引數可以對容器預設的capabilites進行修改:
--cap-add //新增某個capabilites屬性 --cap-del //剔除某個預設的capabilites屬性
cap-add和cap-del可以設定的引數可以通過下面連結查詢到:
https://docs.docker.com/engine/reference/commandline/run/

  • Docker 將gdb除錯需要SYS_PTRACE屬性被禁止掉了,所以gdb在除錯的時候會顯示ptrace被禁止。所以想在docker內部除錯gdb解決辦法就是create和run的時候帶上–cap-add sys_ptrace*
    例如:
docker run  -it --cap-add sys_ptrace centos:latest /bin/bash
root@7f5a2130e975>cd /home/
root@7fa2130e975> vi test.c
#include <stdio.h>
int main(){
     int i = 0;
    printf(“Testing begin
”);
    While(1){
       printf(“Loop cnt:%d
”,i++);
       sleep(10);   
    }
 }

root@7fa2130975>gcc -c -g -o test.o test.c
root@7fa2130975>gcc -o test -g test.o
root@7fa2130975>./test&
[1] 18
root@7fa2130975>gdb attach 18
//ok可以除錯了  

但是這並不是問題的全部,對於上述測試程式,如果執行下面命令gdb又有告警出來

root@7fa2130975>gdb ./test
(gdb) r
Warning:Error disabling address space randomization:Operation not permitted

雖然依然可以除錯,但是我們還是需要搞清楚上述告警的意思。地址隨機化是linux一項安全特性,它允許核心程式啟動每次載入庫的時候都在隨機化的分佈在程式虛擬記憶體地址空間上(早期固定的庫要載入到固定地方,如果固定地方被佔用才載入到別地方。會造成多次載入程式,其庫地址都不變。如此有安全隱患)。在gdb除錯中gdb預設需要關閉linux的地址隨機化功能,可以通過gdb
命令set disable-randomization off關閉。如果在地址隨機化下除錯同一段程式,多次run時候可以看到它的執行地址和函式地址不一致,這沒有什麼太大的問題。問題可以結束了
關於gdb 設定地址隨機化開關詳情見下面連結:
http://visualgdb.com/gdbreference/commands/set_disable-randomization
當然上述告警其實也可以不通過gdb設定來完成,可以通過下面介紹的Docker引數可以達成。

seccomp

Docker預設情況下為每個容器都設定了一個預設的seccom profile。一般情況下無需修改。但是docker依然支援
docker create或者docker run時候通過–security-opt seccomp=xxx引數來設定docker容器的seccomp策略。
xxx可以是一個json格式檔案,裡面定義了docker容器每個具體的seccomp規則。也可以是字元unconfined表示關閉預設的docker seccomp 規則。
可以通過下面命令徹底關閉docker預設seccomp引入的任何限制
docker run -it --security-opt seccomp=unconfined centos:lastes
在執行上述gdb 除錯命令run一個程式,告警資訊終於徹底消失了。
Docker設定的seccomp 預設profile規則可以通過如下連結查詢到:
https://docs.docker.com/engine/security/seccomp/
本文就不再做詳細展開了。

Docker背後的實現技術

Docker實現seccomp控制

從Linux
2.6.23開始支援這種特性對程式能夠使用的系統呼叫進行控制,如此可以進行一些安全性策略。sandbox就是依賴於此技術。docker梳理了Linux的系統呼叫,從300+個系統呼叫中遮蔽掉了44個系統呼叫,但是又最大程度的不影響正常的應用使用系統。

  • 在Docker
    engine上有一個叫seccomp模組提供了為docker提供預設seccomp規則(GetDefaultProfile()函式),而我們在命令列配置的seccomp屬性會覆蓋預設的seccomp規則(在setSeccomp()函式裡)。最終規則在engine內部建立runc
    spec時刻函式createSpec生產。將seccomp傳遞到oci runtime spec的spec.linux.seccomp欄位裡。
  • 而runc通過seccomp庫的InitSeccomp()函式,在Init一個容器時刻和exec觸發的將一個程式setns到容器內部時刻呼叫此函式 將seccomp屬性設定到容器的init程式或者exec程式裡。
  • Linux程式屬性裡有seccomp屬性,此屬性也會父子繼承,通過/proc/$pid/status裡可以看到程式的seccomp屬性。

Docker實現capabilities

從Linux
2.1開始支援的特性,將超級使用者的許可權劃分為多個組,每個程式都有一個capabilities屬性,子程式從自己的父程式中基礎capacities。這個特性和sudo不一樣,因為sudo控制粒度太粗;而capabilities控制粒度很精細。linux有一系列的呼叫可以設定、檢視,清除和比較程式的capabilities。可以通過:
man cap_set_flag
來檢視這一系列的系統呼叫。而具體程式的capacities可以通過/proc/$pid/status中:
Capxxx欄位看到,本文就不再展開。感興趣的朋友可以參考
https://www.cnblogs.com/iamfy/archive/2012/09/20/2694977.html

  • 在oci runtime
    spec裡規定了spec.Process.Capabilities屬性。runc中finializeNamespace()根據此屬性對程式的capabilities進行設定。此函式會在init容器和exec加一個程式到容器時刻呼叫。

相關文章