Udev

無法啟動 chrome 瀏覽器 - 綁定失敗:權限被拒絕

  • July 7, 2020

我正在使用啟用的grsecurity核心:CONFIG_GRKERNSEC_SOCKET_SERVER

[*] Socket restrictions  
 [ ]   Deny any sockets to group (NEW)  
 [ ]   Deny client sockets to group (NEW)
 [*]   Deny server sockets to group

這可以防止使用者創建“伺服器”套接字(即啟動 Apache),但允許打開客戶端套接字(即 Firefox)。

事實上,所有網路客戶端都可以正常工作(Firefox、telnet、ssh、nc、w3m、..)。只有 Chrome 瀏覽器 (Chromium) 不起作用。

從命令行啟動 chrome 時,出現以下錯誤:

ERROR:address_tracker_linux.cc(138)] Could not bind NETLINK socket: Permission denied
libudev: udev_monitor_enable_receiving: bind failed: Permission denied
FATAL:udev_linux.cc(31)] Check failed: 0 == ret (0 vs. -1)
Aborted

在日誌中,我看到:

grsec: denied bind() by /usr/lib/chromium/chromium[NetworkChangeNo:3920]
grsec: denied bind() by /usr/lib/chromium/chromium[WorkerPool/3922:3922]
grsec: denied bind() by /usr/lib/chromium/chromium[Chrome_IOThread:3934]
grsec: denied bind() by /usr/lib/chromium/chromium[NetworkChangeNo:3966]
grsec: denied bind() by /usr/lib/chromium/chromium[WorkerPool/3968:3968]
grsec: denied bind() by /usr/lib/chromium/chromium[Chrome_IOThread:3980]

有人可以解釋一下,為什麼 chrome 無法啟動,而所有其他客戶端(Firefox)都可以正常工作?

我在 Debian Wheezy(64 位)上使用 Chrome (Chromium) 37

Chromium 無法啟動,因為拒絕伺服器套接字也會拒絕AF_NETLINK套接字,並且由於某種原因,Chromium 需要與udev需要AF_NETLINK套接字的 進行通信。我沒有明顯的權威來源,但我會嘗試使用底層原始碼從基本原理進行解釋,並希望在此過程中不會犯太多錯誤。

我開始調查第一條錯誤消息。在 Chromium 中產生錯誤消息的程式碼是https://src.chromium.org/svn/trunk/src/net/base/address_tracker_linux.cc第 138 行:

 // Request notifications.
 struct sockaddr_nl addr = {};
 addr.nl_family = AF_NETLINK;
 addr.nl_pid = getpid();
 // TODO(szym): Track RTMGRP_LINK as well for ifi_type, http://crbug.com/113993
 addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_NOTIFY |
     RTMGRP_LINK;
 int rv = bind(netlink_fd_,
               reinterpret_cast<struct sockaddr*>(&addr),
               sizeof(addr));
 if (rv < 0) {
   PLOG(ERROR) << "Could not bind NETLINK socket";
   AbortAndForceOnline();
   return;
 }

實際與 grsec 相關的故障發生在bind()呼叫中,該呼叫嘗試建立一個 netlink 套接字,只要介面 IPv4 或 IPv6 地址發生變化以及鏈路狀態發生變化,該套接字就會接收通知。

此呼叫由核心中的系統呼叫處理,在net/socket.c

SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
       struct socket *sock;
       struct sockaddr_storage address;
       int err, fput_needed;

這聲明了系統呼叫和一些局部變數。您可以看到 syscall 聲明與bind()Chromium 程式碼中的呼叫匹配:int fd, struct sockaddr __user * umyaddr, int addrlen.

       sock = sockfd_lookup_light(fd, &err, &fput_needed);
       if (sock) {

這會從文件描述符中查找套接字。如果找到套接字…

               err = move_addr_to_kernel(umyaddr, addrlen, &address);
               if (err >= 0) {

這會將提供的數據從使用者空間複製到核心空間。如果沒有錯誤…

                       err = security_socket_bind(sock,
                                                  (struct sockaddr *)&address,
                                                  addrlen);
                       if (!err)

這使任何載入的 LSM(SELinux 等)都有機會檢查是否允許呼叫。如果是這樣的話…

                               err = sock->ops->bind(sock,
                                                     (struct sockaddr *)
                                                     &address, addrlen);

綁定在別處進行,至此我們對標準核心程式碼的分析就完成了。

許多地方的grsec更新檔net/socket.c;特別是,在 LSM 安全檢查之前,它添加了自己的檢查(參見https://grsecurity.net/test/grsecurity-3.0-3.18.6-201502062100.patch;搜尋SYSCALL_DEFINE3(bind):

                       if (gr_handle_sock_server((struct sockaddr *)&address)) {
                               err = -EACCES;
                               goto error;
                       }
                       err = gr_search_bind(sock, (struct sockaddr_in *)&address);
                       if (err)
                               goto error;

第一個檢查是與此處相關的檢查;它呼叫gr_handle_sock_server()

gr_handle_sock_server(const struct sockaddr *sck)
{
#ifdef CONFIG_GRKERNSEC_SOCKET_SERVER
      if (grsec_enable_socket_server &&
          in_group_p(grsec_socket_server_gid) &&
          sck && (sck->sa_family != AF_UNIX) &&
          (sck->sa_family != AF_LOCAL)) {
              gr_log_noargs(GR_DONT_AUDIT, GR_BIND_MSG);
              return -EACCES;
      }
#endif
      return 0;
}

這實現了“拒絕伺服器套接字分組”檢查。正如評論中驗證的那樣,在您的系統grsec_enable_socket_server上是 1,因此當作為組 1001 執行時,if成功(sck->sa_family == AF_NETLINK在這種情況下),並且訪問被拒絕。

回到 Chromium 程式碼,這會記錄一條錯誤消息並呼叫AbortAndForceOnline(),這只是進行設置,以便瀏覽器認為它線上。所以這並不能解釋啟動失敗。

在進一步推動之前,我試圖重現失敗。為此,我進行了調整authbind,以防止AF_NETLINK綁定;在函式中libauthbind.cbind()我在第一個函式中添加了case一個switch

 case AF_NETLINK:
   puts("Denying AF_NETLINK");
   return -EACCES;

使用生成的庫執行重現了失敗:

% LD_PRELOAD=/usr/lib/authbind/libauthbind.so.1 chromium
Denying AF_NETLINK
[15858:15876:0214/160730:ERROR:address_tracker_linux.cc(154)] Could not bind NETLINK socket: Success
Denying AF_NETLINK
libudev: udev_monitor_enable_receiving: bind failed: No such file or directory
[15858:15890:0214/160730:FATAL:udev_linux.cc(29)] Check failed: 0 == ret (0 vs. -2)
zsh: abort      LD_PRELOAD=/usr/lib/authbind/libauthbind.so.1 chromium

(出現奇怪的錯誤消息“成功”和“沒有這樣的文件目錄”是因為我沒有設置errno。)

所以中止確實與bind(). 檢查udev_linux.cc第 29 行顯示

 int ret = udev_monitor_enable_receiving(monitor_.get());
 CHECK_EQ(0, ret);

ret這裡是負面udev_monitor_enable_receiving()的,因為無法綁定 netlink 套接字,並CHECK_EQ在此處導致斷言失敗(有關實現,請參見https://src.chromium.org/svn/trunk/src/base/logging.h)。這會產生一個中止信號,並且 Chromium 會以某種“已中止”消息退出,具體取決於所使用的 shell。

引用自:https://unix.stackexchange.com/questions/183211