无法启动 Chrome 浏览器 - 绑定失败:权限被拒绝

无法启动 Chrome 浏览器 - 绑定失败:权限被拒绝

我正在使用启用的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

答案1

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;

这声明了系统调用和一些局部变量。您可以看到系统调用声明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);

绑定在其他地方进行,我们已经完成了对标准内核代码的分析。

grsecnet/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.c,在bind()函数中我添加了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。

相关内容