Linuxで一定期間経過したプロセスをkill -9するデーモン

 共用サーバなどで、一定時間が経過したプログラムをkillしたい場合がある。
 ただ、manやgccまでkillしてしまうと可哀そうなので、特定のコードが含まれる場合のみにしたいという要件とかを含む。

/*
 * proc_killer.c - A daemon that kills processes outside the allow list after a certain period of time.
 *
 * Requirements:
 * - Runs as root only.
 * - Allow list (/etc/poc_check/proc_allow_list)
 * - Monitored user list (/etc/poc_check/monitor_users)
 * - Outputs kill log to a file (/var/log/proc_killer.log)
 *
 * Compilation:
 * gcc -O2 -o proc_killer proc_killer.c
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <signal.h>
#include <time.h>
#include <errno.h>

#define ALLOW_LIST_FILE "/etc/poc_check/proc_allow_list"
#define USER_LIST_FILE  "/etc/poc_check/monitor_users"
#define LOG_FILE        "/var/log/proc_killer.log"

#define CHECK_INTERVAL  30
#define MAX_SECONDS     300
#define GRACE_PERIOD    5

typedef struct Node {
    char *value;
    struct Node *next;
} Node;

Node *load_list(const char *path) {
    FILE *fp = fopen(path, "r");
    if (!fp) return NULL;
    Node *head = NULL, *cur = NULL;
    char buf[256];
    while (fgets(buf, sizeof(buf), fp)) {
        buf[strcspn(buf, "\r\n")] = 0;
        if (strlen(buf) == 0) continue;
        Node *n = malloc(sizeof(Node));
        n->value = strdup(buf);
        n->next = NULL;
        if (!head) head = n;
        else cur->next = n;
        cur = n;
    }
    fclose(fp);
    return head;
}

int in_list(Node *head, const char *val) {
    for (Node *n = head; n; n = n->next) {
        if (strcmp(n->value, val) == 0) return 1;
    }
    return 0;
}

void free_list(Node *head) {
    while (head) {
        Node *tmp = head;
        head = head->next;
        free(tmp->value);
        free(tmp);
    }
}

void log_kill(const char *user, pid_t pid, const char *cmd, int etime, const char *sig) {
    FILE *fp = fopen(LOG_FILE, "a");
    if (!fp) return;
    time_t now = time(NULL);
    char ts[64];
    strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", localtime(&now));
    fprintf(fp, "%s KILL[%s] USER=%s PID=%d CMD=%s ETIME=%ds\n",
            ts, sig, user, pid, cmd, etime);
    fclose(fp);
}

int get_process_info(pid_t pid, char *comm, size_t clen, uid_t *uid, int *etime) {
    char path[256], buf[1024];
    FILE *fp;

    snprintf(path, sizeof(path), "/proc/%d/stat", pid);
    fp = fopen(path, "r");
    if (!fp) return -1;
    if (!fgets(buf, sizeof(buf), fp)) { fclose(fp); return -1; }
    fclose(fp);

    char comm_raw[256];
    long long start_ticks;
    sscanf(buf, "%*d (%255[^)]) %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %*u %*u %*u %*u %*u %*u %*u %lld",
           comm_raw, &start_ticks);
    strncpy(comm, comm_raw, clen);

    // UID
    struct stat st;
    snprintf(path, sizeof(path), "/proc/%d", pid);
    if (stat(path, &st) == -1) return -1;
    *uid = st.st_uid;

    double uptime = 0.0;
    fp = fopen("/proc/uptime", "r");
    if (!fp) return -1;
    if (fscanf(fp, "%lf", &uptime) != 1) { fclose(fp); return -1; }
    fclose(fp);

    long hz = sysconf(_SC_CLK_TCK);
    *etime = (int)(uptime - (start_ticks / hz));
    if (*etime < 0) *etime = 0;

    return 0;
}

void monitor(Node *allow, Node *users) {
    DIR *dir = opendir("/proc");
    if (!dir) return;
    struct dirent *ent;

    while ((ent = readdir(dir))) {
        if (!isdigit(ent->d_name[0])) continue;
        pid_t pid = atoi(ent->d_name);

        char comm[256];
        uid_t uid;
        int etime;
        if (get_process_info(pid, comm, sizeof(comm), &uid, &etime) != 0) continue;

        struct passwd *pw = getpwuid(uid);
        if (!pw) continue;
        const char *uname = pw->pw_name;

        if (users && !in_list(users, uname)) continue;

        if (allow && in_list(allow, comm)) continue;

        if (etime > MAX_SECONDS) {
            if (kill(pid, SIGTERM) == 0) {
                log_kill(uname, pid, comm, etime, "SIGTERM");
                sleep(GRACE_PERIOD);
                if (kill(pid, 0) == 0) {
                    if (kill(pid, SIGKILL) == 0) {
                        log_kill(uname, pid, comm, etime, "SIGKILL");
                    }
                }
            }
        }
    }
    closedir(dir);
}

void daemonize() {
    pid_t pid = fork();
    if (pid < 0) exit(EXIT_FAILURE);
    if (pid > 0) exit(EXIT_SUCCESS);

    if (setsid() < 0) exit(EXIT_FAILURE);

    pid = fork();
    if (pid < 0) exit(EXIT_FAILURE);
    if (pid > 0) exit(EXIT_SUCCESS);

    umask(0);
    chdir("/");

    for (int x = sysconf(_SC_OPEN_MAX); x>=0; x--) {
        close(x);
    }
}

int main() {
    if (geteuid() != 0) {
        fprintf(stderr, "Error: root only.\n");
        return 1;
    }

    daemonize();

    Node *allow = load_list(ALLOW_LIST_FILE);
    Node *users = load_list(USER_LIST_FILE);

    while (1) {
        monitor(allow, users);
        sleep(CHECK_INTERVAL);
    }

    free_list(allow);
    free_list(users);
    return 0;
}

導入方法

gcc -O2 -o /usr/local/sbin/proc_killer proc_killer.c

systemd

[Unit]
Description=Process Killer Daemon
After=multi-user.target

[Service]
ExecStart=/usr/local/sbin/proc_killer
Restart=always
User=root

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now proc_killer.service

/etc/systemd/system/proc_killer.service
にこんな感じで記入する

[Unit]
Description=Process Killer Daemon
After=multi-user.target

[Service]
ExecStart=/usr/local/sbin/proc_killer
Restart=always
User=root

[Install]
WantedBy=multi-user.target

起動

systemctl daemon-reload
systemctl enable --now proc_killer.service

許可しておべきコマンドを/etc/poc_check/proc_allow_listに書き込む。

bash
sh
zsh
csh
tcsh
httpd
apache2
php-cgi
php-fpm
perl
python
python3
ruby
proftpd
vsftpd
pure-ftpd
postfix
sendmail
exim
dovecot
courier
imapd
pop3d
sshd
cron
crond
atd
syslogd
rsyslogd
systemd
init
ls
cat
less
more
tail
head
grep
awk
sed
vim
vi
nano
scp
sftp
rsync
wget
curl
tar
gzip
bzip2
xz
zip
unzip
gcc
g++
make
perl
python3
man

例(test.cgi)を置く

#!/usr/bin/perl
print "Content-type:text/plain\n\n";
print "時が止まる\n";
sleep 999999999;
print "よくぞ耐えた";
exit;

プロセスが死ぬと下記ファイルにログが残る

[Thu Sep 11 20:05:30 2025] [error] [client 192.168.1.2] Premature end of script headers: foo.cgi
[Thu Sep 11 20:10:35 2025] [error] [client 192.168.1.2] (32)Broken pipe: write_data failed
[Thu Sep 11 20:10:35 2025] [error] [client 192.168.1.2] child process 12016 exited due to signal 9 (SIGKILL)

 共用サーバで「sleep 99999」などを実行する変な奴対策として作った。rootで起動したら放置しておく。
 一般ユーザが変なコマンドを実行したらkillされる(はず)。


いいなと思ったら応援しよう!

コメント

コメントするには、 ログイン または 会員登録 をお願いします。
Linuxで一定期間経過したプロセスをkill -9するデーモン|𝘼𝙆𝘼𝙄 𝙆𝙄𝙏𝙎𝙐𝙉𝙀
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1