| // SPDX-License-Identifier: GPL-2.0-only |
| |
| #include <sys/types.h> |
| #include <sys/epoll.h> |
| #include <sys/socket.h> |
| #include <linux/netlink.h> |
| #include <linux/connector.h> |
| #include <linux/cn_proc.h> |
| |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <strings.h> |
| #include <errno.h> |
| #include <signal.h> |
| #include <string.h> |
| |
| #include "../kselftest.h" |
| |
| #define NL_MESSAGE_SIZE (sizeof(struct nlmsghdr) + sizeof(struct cn_msg) + \ |
| sizeof(struct proc_input)) |
| #define NL_MESSAGE_SIZE_NF (sizeof(struct nlmsghdr) + sizeof(struct cn_msg) + \ |
| sizeof(int)) |
| |
| #define MAX_EVENTS 1 |
| |
| volatile static int interrupted; |
| static int nl_sock, ret_errno, tcount; |
| static struct epoll_event evn; |
| |
| static int filter; |
| |
| #ifdef ENABLE_PRINTS |
| #define Printf printf |
| #else |
| #define Printf ksft_print_msg |
| #endif |
| |
| int send_message(void *pinp) |
| { |
| char buff[NL_MESSAGE_SIZE]; |
| struct nlmsghdr *hdr; |
| struct cn_msg *msg; |
| |
| hdr = (struct nlmsghdr *)buff; |
| if (filter) |
| hdr->nlmsg_len = NL_MESSAGE_SIZE; |
| else |
| hdr->nlmsg_len = NL_MESSAGE_SIZE_NF; |
| hdr->nlmsg_type = NLMSG_DONE; |
| hdr->nlmsg_flags = 0; |
| hdr->nlmsg_seq = 0; |
| hdr->nlmsg_pid = getpid(); |
| |
| msg = (struct cn_msg *)NLMSG_DATA(hdr); |
| msg->id.idx = CN_IDX_PROC; |
| msg->id.val = CN_VAL_PROC; |
| msg->seq = 0; |
| msg->ack = 0; |
| msg->flags = 0; |
| |
| if (filter) { |
| msg->len = sizeof(struct proc_input); |
| ((struct proc_input *)msg->data)->mcast_op = |
| ((struct proc_input *)pinp)->mcast_op; |
| ((struct proc_input *)msg->data)->event_type = |
| ((struct proc_input *)pinp)->event_type; |
| } else { |
| msg->len = sizeof(int); |
| *(int *)msg->data = *(enum proc_cn_mcast_op *)pinp; |
| } |
| |
| if (send(nl_sock, hdr, hdr->nlmsg_len, 0) == -1) { |
| ret_errno = errno; |
| perror("send failed"); |
| return -3; |
| } |
| return 0; |
| } |
| |
| int register_proc_netlink(int *efd, void *input) |
| { |
| struct sockaddr_nl sa_nl; |
| int err = 0, epoll_fd; |
| |
| nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR); |
| |
| if (nl_sock == -1) { |
| ret_errno = errno; |
| perror("socket failed"); |
| return -1; |
| } |
| |
| bzero(&sa_nl, sizeof(sa_nl)); |
| sa_nl.nl_family = AF_NETLINK; |
| sa_nl.nl_groups = CN_IDX_PROC; |
| sa_nl.nl_pid = getpid(); |
| |
| if (bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl)) == -1) { |
| ret_errno = errno; |
| perror("bind failed"); |
| return -2; |
| } |
| |
| epoll_fd = epoll_create1(EPOLL_CLOEXEC); |
| if (epoll_fd < 0) { |
| ret_errno = errno; |
| perror("epoll_create1 failed"); |
| return -2; |
| } |
| |
| err = send_message(input); |
| |
| if (err < 0) |
| return err; |
| |
| evn.events = EPOLLIN; |
| evn.data.fd = nl_sock; |
| if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, nl_sock, &evn) < 0) { |
| ret_errno = errno; |
| perror("epoll_ctl failed"); |
| return -3; |
| } |
| *efd = epoll_fd; |
| return 0; |
| } |
| |
| static void sigint(int sig) |
| { |
| interrupted = 1; |
| } |
| |
| int handle_packet(char *buff, int fd, struct proc_event *event) |
| { |
| struct nlmsghdr *hdr; |
| |
| hdr = (struct nlmsghdr *)buff; |
| |
| if (hdr->nlmsg_type == NLMSG_ERROR) { |
| perror("NLMSG_ERROR error\n"); |
| return -3; |
| } else if (hdr->nlmsg_type == NLMSG_DONE) { |
| event = (struct proc_event *) |
| ((struct cn_msg *)NLMSG_DATA(hdr))->data; |
| tcount++; |
| switch (event->what) { |
| case PROC_EVENT_EXIT: |
| Printf("Exit process %d (tgid %d) with code %d, signal %d\n", |
| event->event_data.exit.process_pid, |
| event->event_data.exit.process_tgid, |
| event->event_data.exit.exit_code, |
| event->event_data.exit.exit_signal); |
| break; |
| case PROC_EVENT_FORK: |
| Printf("Fork process %d (tgid %d), parent %d (tgid %d)\n", |
| event->event_data.fork.child_pid, |
| event->event_data.fork.child_tgid, |
| event->event_data.fork.parent_pid, |
| event->event_data.fork.parent_tgid); |
| break; |
| case PROC_EVENT_EXEC: |
| Printf("Exec process %d (tgid %d)\n", |
| event->event_data.exec.process_pid, |
| event->event_data.exec.process_tgid); |
| break; |
| case PROC_EVENT_UID: |
| Printf("UID process %d (tgid %d) uid %d euid %d\n", |
| event->event_data.id.process_pid, |
| event->event_data.id.process_tgid, |
| event->event_data.id.r.ruid, |
| event->event_data.id.e.euid); |
| break; |
| case PROC_EVENT_GID: |
| Printf("GID process %d (tgid %d) gid %d egid %d\n", |
| event->event_data.id.process_pid, |
| event->event_data.id.process_tgid, |
| event->event_data.id.r.rgid, |
| event->event_data.id.e.egid); |
| break; |
| case PROC_EVENT_SID: |
| Printf("SID process %d (tgid %d)\n", |
| event->event_data.sid.process_pid, |
| event->event_data.sid.process_tgid); |
| break; |
| case PROC_EVENT_PTRACE: |
| Printf("Ptrace process %d (tgid %d), Tracer %d (tgid %d)\n", |
| event->event_data.ptrace.process_pid, |
| event->event_data.ptrace.process_tgid, |
| event->event_data.ptrace.tracer_pid, |
| event->event_data.ptrace.tracer_tgid); |
| break; |
| case PROC_EVENT_COMM: |
| Printf("Comm process %d (tgid %d) comm %s\n", |
| event->event_data.comm.process_pid, |
| event->event_data.comm.process_tgid, |
| event->event_data.comm.comm); |
| break; |
| case PROC_EVENT_COREDUMP: |
| Printf("Coredump process %d (tgid %d) parent %d, (tgid %d)\n", |
| event->event_data.coredump.process_pid, |
| event->event_data.coredump.process_tgid, |
| event->event_data.coredump.parent_pid, |
| event->event_data.coredump.parent_tgid); |
| break; |
| default: |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| int handle_events(int epoll_fd, struct proc_event *pev) |
| { |
| char buff[CONNECTOR_MAX_MSG_SIZE]; |
| struct epoll_event ev[MAX_EVENTS]; |
| int i, event_count = 0, err = 0; |
| |
| event_count = epoll_wait(epoll_fd, ev, MAX_EVENTS, -1); |
| if (event_count < 0) { |
| ret_errno = errno; |
| if (ret_errno != EINTR) |
| perror("epoll_wait failed"); |
| return -3; |
| } |
| for (i = 0; i < event_count; i++) { |
| if (!(ev[i].events & EPOLLIN)) |
| continue; |
| if (recv(ev[i].data.fd, buff, sizeof(buff), 0) == -1) { |
| ret_errno = errno; |
| perror("recv failed"); |
| return -3; |
| } |
| err = handle_packet(buff, ev[i].data.fd, pev); |
| if (err < 0) |
| return err; |
| } |
| return 0; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int epoll_fd, err; |
| struct proc_event proc_ev; |
| struct proc_input input; |
| |
| signal(SIGINT, sigint); |
| |
| if (argc > 2) { |
| printf("Expected 0(assume no-filter) or 1 argument(-f)\n"); |
| exit(KSFT_SKIP); |
| } |
| |
| if (argc == 2) { |
| if (strcmp(argv[1], "-f") == 0) { |
| filter = 1; |
| } else { |
| printf("Valid option : -f (for filter feature)\n"); |
| exit(KSFT_SKIP); |
| } |
| } |
| |
| if (filter) { |
| input.event_type = PROC_EVENT_NONZERO_EXIT; |
| input.mcast_op = PROC_CN_MCAST_LISTEN; |
| err = register_proc_netlink(&epoll_fd, (void*)&input); |
| } else { |
| enum proc_cn_mcast_op op = PROC_CN_MCAST_LISTEN; |
| err = register_proc_netlink(&epoll_fd, (void*)&op); |
| } |
| |
| if (err < 0) { |
| if (err == -2) |
| close(nl_sock); |
| if (err == -3) { |
| close(nl_sock); |
| close(epoll_fd); |
| } |
| exit(1); |
| } |
| |
| while (!interrupted) { |
| err = handle_events(epoll_fd, &proc_ev); |
| if (err < 0) { |
| if (ret_errno == EINTR) |
| continue; |
| if (err == -2) |
| close(nl_sock); |
| if (err == -3) { |
| close(nl_sock); |
| close(epoll_fd); |
| } |
| exit(1); |
| } |
| } |
| |
| if (filter) { |
| input.mcast_op = PROC_CN_MCAST_IGNORE; |
| send_message((void*)&input); |
| } else { |
| enum proc_cn_mcast_op op = PROC_CN_MCAST_IGNORE; |
| send_message((void*)&op); |
| } |
| |
| close(epoll_fd); |
| close(nl_sock); |
| |
| printf("Done total count: %d\n", tcount); |
| exit(0); |
| } |