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