Systemd-Journald

journald 如何知道產生日誌數據的程序的 PID?

  • December 29, 2018

當我查看時journalctl,它會告訴我日誌條目的 PID 和程序名稱(或服務名稱?)。

然後我想知道,日誌是由其他程序創建的,當程序可能只將原始字元串寫入正在偵聽的 unix 域套接字時,如何systemd-journald知道這些程序的 PID systemd-journald。此外,是否sytemd-journald始終使用相同的技術來檢測一段日誌數據的 PID,即使程序正在使用類似的函式生成日誌sd_journal_sendv()

我應該閱讀有關此的任何文件嗎?

我閱讀了 JdeBP 的答案並知道systemd-journald在 Unix Domian Socket 上偵聽,但是即使可以知道發送日誌消息的對等套接字地址,它如何知道 PID?如果該發送套接字被許多非父子程序打開怎麼辦?

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()函式 from 中journald-server.c。預設情況下,syslog(3)標準函式 fromlibcsd_journal_sendv()from都libsystemd將通過套接字發送數據,並且不適用於數據報(無連接)套接字。既不也不接受. _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;
}

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