当我查看 时journalctl
,它告诉我日志条目的 PID 和程序名称(或服务名称?)。
然后我想知道,日志是由其他进程创建的,当进程只能将原始字符串写入正在侦听的unix域套接字时,如何systemd-journald
知道这些进程的PID systemd-journald
。另外,sytemd-journald
即使进程使用诸如 之类的函数生成日志,也始终使用相同的技术来检测一段日志数据的 PIDsd_journal_sendv()
吗?
有什么我应该阅读的文档吗?
我读JdeBP的回答并且知道systemd-journald
侦听 Unix Domian 套接字,但即使可以知道发送日志消息的对等套接字地址,它如何知道 PID?如果发送套接字被许多非父子进程打开怎么办?
答案1
SCM_CREDENTIALS
它通过unix 套接字上的辅助数据接收 pid recvmsg()
,请参阅unix(7)
。不必显式发送凭据。
例子:
$ cc -Wall scm_cred.c -o scm_cred
$ ./scm_cred
scm_cred: received from 10114: pid=10114 uid=2000 gid=2000
有数据的进程CAP_SYS_ADMIN
可以通过 发送它们想要的任何 pid SCM_CREDENTIALS
;在 的情况下systemd-journald
,这意味着他们可以伪造条目,就像由另一个进程记录一样:
# cc -Wall fake.c -o fake
# setcap CAP_SYS_ADMIN+ep fake
$ ./fake `pgrep -f /usr/sbin/sshd`
# journalctl --no-pager -n 1
...
Dec 29 11:04:57 debin sshd[419]: fake log message from 14202
# rm fake
# lsb_release -d
Description: Debian GNU/Linux 9.6 (stretch)
systemd-journald
处理通过辅助数据发送的数据报和凭证server_process_datagram()
函数来自journald-server.c
.默认情况下,标准syslog(3)
函数 fromlibc
和sd_journal_sendv()
fromlibsystemd
都会通过套接字发送数据,并且不适用于数据报(无连接)套接字。既不接受也不接受上的连接。SOCK_DGRAM
getsockopt(SO_PEERCRED)
systemd-journald
rsyslogd
SOCK_STREAM
/dev/log
scm_cred.c
#define _GNU_SOURCE 1
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <err.h>
int main(void){
int fd[2]; pid_t pid;
if(socketpair(AF_LOCAL, SOCK_DGRAM, 0, fd)) err(1, "socketpair");
if((pid = fork()) == -1) err(1, "fork");
if(pid){ /* parent */
int on = 1;
union {
struct cmsghdr h;
char data[CMSG_SPACE(sizeof(struct ucred))];
} buf;
struct msghdr m = {0};
struct ucred *uc = (struct ucred*)CMSG_DATA(&buf.h);
m.msg_control = &buf;
m.msg_controllen = sizeof buf;
if(setsockopt(fd[0], SOL_SOCKET, SO_PASSCRED, &on, sizeof on))
err(1, "setsockopt");
if(recvmsg(fd[0], &m, 0) == -1) err(1, "recvmsg");
warnx("received from %d: pid=%d uid=%d gid=%d", pid,
uc->pid, uc->uid, uc->gid);
}else /* child */
write(fd[1], 0, 0);
return 0;
}
假的c
#define _GNU_SOURCE 1
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <err.h>
int main(int ac, char **av){
union {
struct cmsghdr h;
char data[CMSG_SPACE(sizeof(struct ucred))];
} cm;
int fd; char buf[256];
struct ucred *uc = (struct ucred*)CMSG_DATA(&cm.h);
struct msghdr m = {0};
struct sockaddr_un ua = {AF_UNIX, "/dev/log"};
struct iovec iov = {buf};
if((fd = socket(AF_LOCAL, SOCK_DGRAM, 0)) == -1) err(1, "socket");
if(connect(fd, (struct sockaddr*)&ua, SUN_LEN(&ua))) err(1, "connect");
m.msg_control = &cm;
m.msg_controllen = cm.h.cmsg_len = CMSG_LEN(sizeof(struct ucred));
cm.h.cmsg_level = SOL_SOCKET;
cm.h.cmsg_type = SCM_CREDENTIALS;
uc->pid = ac > 1 ? atoi(av[1]) : getpid();
uc->uid = ac > 2 ? atoi(av[2]) : geteuid();
uc->gid = ac > 3 ? atoi(av[3]) : getegid();
iov.iov_len = snprintf(buf, sizeof buf, "<13>%s from %d",
ac > 4 ? av[4] : "fake log message", getpid());
if(iov.iov_len >= sizeof buf) errx(1, "message too long");
m.msg_iov = &iov;
m.msg_iovlen = 1;
if(sendmsg(fd, &m, 0) == -1) err(1, "sendmsg");
return 0;
}
答案2
内核告诉它。
AF_LOCAL
连接流套接字的原始客户端进程的 EUID、EGID 和 PID/run/systemd/journal/stdout
可通过套接字选项从内核获取SO_PEERCRED
,它使用哪个。 UCSPI-UNIX 工具通过相同的系统调用获取相同的信息。
当然,子服务进程继承其已打开的标准 I/O 文件描述符(当然,除非父服务进程更改了这一点),因此systemd-journald
所有日志输出都具有原始父进程的凭据。
AF_LOCAL
通过套接字生成的日志输出/run/systemd/journal/socket
表明特殊systemd-journald
协议是通过数据报套接字而不是流套接字来的。这个插座SO_PASSCRED
使用套接字选项进行标记以便内核在发送的每个数据报中记录相同的信息,这从每个数据报中拉出经过systemd-journald
。
进一步阅读
getsockopt()
。Linux 程序员手册。 2017年9月15日。socket
。Linux 程序员手册。 2018年2月2日。- 乔纳森·德博因·波拉德 (2017)。
local-stream-socket-accept
。小吃指南。软件。 - 乔纳森·德博因·波拉德 (2015)。 ”环境变量”。 UNIX 客户端-服务器程序接口上的 gen。常见答案。