mirror of
https://github.com/bol-van/zapret.git
synced 2024-12-18 22:11:31 +03:00
361 lines
7.1 KiB
C
361 lines
7.1 KiB
C
#define _GNU_SOURCE
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include "sec.h"
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <grp.h>
|
|
|
|
#ifdef __linux__
|
|
|
|
#include <sys/prctl.h>
|
|
#include <sys/syscall.h>
|
|
#include <linux/seccomp.h>
|
|
#include <linux/filter.h>
|
|
// __X32_SYSCALL_BIT defined in linux/unistd.h
|
|
#include <linux/unistd.h>
|
|
#include <syscall.h>
|
|
#include <errno.h>
|
|
|
|
/************ SECCOMP ************/
|
|
|
|
// block most of the undesired syscalls to harden against code execution
|
|
static long blocked_syscalls[] = {
|
|
#ifdef SYS_execv
|
|
SYS_execv,
|
|
#endif
|
|
SYS_execve,
|
|
#ifdef SYS_execveat
|
|
SYS_execveat,
|
|
#endif
|
|
#ifdef SYS_exec_with_loader
|
|
SYS_exec_with_loader,
|
|
#endif
|
|
#ifdef SYS_osf_execve
|
|
SYS_osf_execve,
|
|
#endif
|
|
#ifdef SYS_uselib
|
|
SYS_uselib,
|
|
#endif
|
|
#ifdef SYS_unlink
|
|
SYS_unlink,
|
|
#endif
|
|
SYS_unlinkat,
|
|
#ifdef SYS_chmod
|
|
SYS_chmod,
|
|
#endif
|
|
SYS_fchmod,SYS_fchmodat,
|
|
#ifdef SYS_chown
|
|
SYS_chown,
|
|
#endif
|
|
#ifdef SYS_chown32
|
|
SYS_chown32,
|
|
#endif
|
|
SYS_fchown,
|
|
#ifdef SYS_fchown32
|
|
SYS_fchown32,
|
|
#endif
|
|
#ifdef SYS_lchown
|
|
SYS_lchown,
|
|
#endif
|
|
#ifdef SYS_lchown32
|
|
SYS_lchown32,
|
|
#endif
|
|
SYS_fchownat,
|
|
#ifdef SYS_symlink
|
|
SYS_symlink,
|
|
#endif
|
|
SYS_symlinkat,
|
|
#ifdef SYS_link
|
|
SYS_link,
|
|
#endif
|
|
SYS_linkat,
|
|
SYS_truncate,
|
|
#ifdef SYS_truncate64
|
|
SYS_truncate64,
|
|
#endif
|
|
SYS_ftruncate,
|
|
#ifdef SYS_ftruncate64
|
|
SYS_ftruncate64,
|
|
#endif
|
|
#ifdef SYS_mknod
|
|
SYS_mknod,
|
|
#endif
|
|
SYS_mknodat,
|
|
#ifdef SYS_mkdir
|
|
SYS_mkdir,
|
|
#endif
|
|
SYS_mkdirat,
|
|
#ifdef SYS_rmdir
|
|
SYS_rmdir,
|
|
#endif
|
|
#ifdef SYS_rename
|
|
SYS_rename,
|
|
#endif
|
|
#ifdef SYS_renameat2
|
|
SYS_renameat2,
|
|
#endif
|
|
#ifdef SYS_renameat
|
|
SYS_renameat,
|
|
#endif
|
|
#ifdef SYS_readdir
|
|
SYS_readdir,
|
|
#endif
|
|
#ifdef SYS_getdents
|
|
SYS_getdents,
|
|
#endif
|
|
#ifdef SYS_getdents64
|
|
SYS_getdents64,
|
|
#endif
|
|
#ifdef SYS_process_vm_readv
|
|
SYS_process_vm_readv,
|
|
#endif
|
|
#ifdef SYS_process_vm_writev
|
|
SYS_process_vm_writev,
|
|
#endif
|
|
#ifdef SYS_process_madvise
|
|
SYS_process_madvise,
|
|
#endif
|
|
SYS_kill, SYS_ptrace
|
|
};
|
|
#define BLOCKED_SYSCALL_COUNT (sizeof(blocked_syscalls)/sizeof(*blocked_syscalls))
|
|
|
|
static void set_filter(struct sock_filter *filter, __u16 code, __u8 jt, __u8 jf, __u32 k)
|
|
{
|
|
filter->code = code;
|
|
filter->jt = jt;
|
|
filter->jf = jf;
|
|
filter->k = k;
|
|
}
|
|
// deny all blocked syscalls
|
|
static bool set_seccomp(void)
|
|
{
|
|
#ifdef __X32_SYSCALL_BIT
|
|
#define SECCOMP_PROG_SIZE (6 + BLOCKED_SYSCALL_COUNT)
|
|
#else
|
|
#define SECCOMP_PROG_SIZE (5 + BLOCKED_SYSCALL_COUNT)
|
|
#endif
|
|
struct sock_filter sockf[SECCOMP_PROG_SIZE];
|
|
struct sock_fprog prog = { .len = SECCOMP_PROG_SIZE, .filter = sockf };
|
|
int i,idx=0;
|
|
|
|
set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, arch_nr);
|
|
#ifdef __X32_SYSCALL_BIT
|
|
// x86 only
|
|
set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail
|
|
set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr);
|
|
set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 1 + BLOCKED_SYSCALL_COUNT, 0, __X32_SYSCALL_BIT - 1); // fail
|
|
#else
|
|
set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 2 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail
|
|
set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr);
|
|
#endif
|
|
|
|
/*
|
|
// ! THIS IS NOT WORKING BECAUSE perror() in glibc dups() stderr
|
|
set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3, SYS_write); // special check for write call
|
|
set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_arg(0)); // fd
|
|
set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 2 + BLOCKED_SYSCALL_COUNT, 0, 2); // 1 - stdout, 2 - stderr. greater are bad
|
|
set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); // reload syscall_nr
|
|
*/
|
|
for(i=0 ; i<BLOCKED_SYSCALL_COUNT ; i++)
|
|
{
|
|
set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, BLOCKED_SYSCALL_COUNT-i, 0, blocked_syscalls[i]);
|
|
}
|
|
set_filter(&prog.filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_ALLOW); // success case
|
|
set_filter(&prog.filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_KILL); // fail case
|
|
return prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) >= 0;
|
|
}
|
|
|
|
bool sec_harden(void)
|
|
{
|
|
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
|
|
{
|
|
DLOG_PERROR("PR_SET_NO_NEW_PRIVS(prctl)");
|
|
return false;
|
|
}
|
|
#if ARCH_NR!=0
|
|
if (!set_seccomp())
|
|
{
|
|
DLOG_PERROR("seccomp");
|
|
if (errno==EINVAL) DLOG_ERR("seccomp: this can be safely ignored if kernel does not support seccomp\n");
|
|
return false;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool checkpcap(uint64_t caps)
|
|
{
|
|
if (!caps) return true; // no special caps reqd
|
|
|
|
struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()};
|
|
struct __user_cap_data_struct cd[2];
|
|
uint32_t c0 = (uint32_t)caps;
|
|
uint32_t c1 = (uint32_t)(caps>>32);
|
|
|
|
return !capget(&ch,cd) && (cd[0].effective & c0)==c0 && (cd[1].effective & c1)==c1;
|
|
}
|
|
bool setpcap(uint64_t caps)
|
|
{
|
|
struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()};
|
|
struct __user_cap_data_struct cd[2];
|
|
|
|
cd[0].effective = cd[0].permitted = (uint32_t)caps;
|
|
cd[0].inheritable = 0;
|
|
cd[1].effective = cd[1].permitted = (uint32_t)(caps>>32);
|
|
cd[1].inheritable = 0;
|
|
|
|
return !capset(&ch,cd);
|
|
}
|
|
int getmaxcap(void)
|
|
{
|
|
int maxcap = CAP_LAST_CAP;
|
|
FILE *F = fopen("/proc/sys/kernel/cap_last_cap", "r");
|
|
if (F)
|
|
{
|
|
int n = fscanf(F, "%d", &maxcap);
|
|
fclose(F);
|
|
}
|
|
return maxcap;
|
|
|
|
}
|
|
bool dropcaps(void)
|
|
{
|
|
uint64_t caps = 0;
|
|
int maxcap = getmaxcap();
|
|
|
|
if (setpcap(caps|(1<<CAP_SETPCAP)))
|
|
{
|
|
for (int cap = 0; cap <= maxcap; cap++)
|
|
{
|
|
if (prctl(PR_CAPBSET_DROP, cap)<0)
|
|
{
|
|
DLOG_ERR("could not drop bound cap %d\n", cap);
|
|
DLOG_PERROR("cap_drop_bound");
|
|
}
|
|
}
|
|
}
|
|
// now without CAP_SETPCAP
|
|
if (!setpcap(caps))
|
|
{
|
|
DLOG_PERROR("setpcap");
|
|
return checkpcap(caps);
|
|
}
|
|
return true;
|
|
}
|
|
#else // __linux__
|
|
|
|
bool sec_harden(void)
|
|
{
|
|
// noop
|
|
return true;
|
|
}
|
|
|
|
#endif // __linux__
|
|
|
|
|
|
|
|
bool can_drop_root(void)
|
|
{
|
|
#ifdef __linux__
|
|
// has some caps
|
|
return checkpcap((1<<CAP_SETUID)|(1<<CAP_SETGID)|(1<<CAP_SETPCAP));
|
|
#else
|
|
// effective root
|
|
return !geteuid();
|
|
#endif
|
|
}
|
|
|
|
bool droproot(uid_t uid, gid_t gid)
|
|
{
|
|
#ifdef __linux__
|
|
if (prctl(PR_SET_KEEPCAPS, 1L))
|
|
{
|
|
DLOG_PERROR("prctl(PR_SET_KEEPCAPS)");
|
|
return false;
|
|
}
|
|
#endif
|
|
// drop all SGIDs
|
|
if (setgroups(0,NULL))
|
|
{
|
|
DLOG_PERROR("setgroups");
|
|
return false;
|
|
}
|
|
if (setgid(gid))
|
|
{
|
|
DLOG_PERROR("setgid");
|
|
return false;
|
|
}
|
|
if (setuid(uid))
|
|
{
|
|
DLOG_PERROR("setuid");
|
|
return false;
|
|
}
|
|
#ifdef __linux__
|
|
return dropcaps();
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
void print_id(void)
|
|
{
|
|
int i,N;
|
|
gid_t g[128];
|
|
|
|
DLOG_CONDUP("Running as UID=%u GID=",getuid());
|
|
N=getgroups(sizeof(g)/sizeof(*g),g);
|
|
if (N>0)
|
|
{
|
|
for(i=0;i<N;i++)
|
|
DLOG_CONDUP(i==(N-1) ? "%u" : "%u,", g[i]);
|
|
DLOG_CONDUP("\n");
|
|
}
|
|
else
|
|
DLOG_CONDUP("%u\n",getgid());
|
|
}
|
|
|
|
void daemonize(void)
|
|
{
|
|
int pid;
|
|
|
|
pid = fork();
|
|
if (pid == -1)
|
|
{
|
|
DLOG_PERROR("fork");
|
|
exit(2);
|
|
}
|
|
else if (pid != 0)
|
|
exit(0);
|
|
|
|
if (setsid() == -1)
|
|
exit(2);
|
|
if (chdir("/") == -1)
|
|
exit(2);
|
|
close(STDIN_FILENO);
|
|
close(STDOUT_FILENO);
|
|
close(STDERR_FILENO);
|
|
/* redirect fd's 0,1,2 to /dev/null */
|
|
open("/dev/null", O_RDWR);
|
|
int fd;
|
|
/* stdin */
|
|
fd = dup(0);
|
|
/* stdout */
|
|
fd = dup(0);
|
|
/* stderror */
|
|
}
|
|
|
|
bool writepid(const char *filename)
|
|
{
|
|
FILE *F;
|
|
if (!(F = fopen(filename, "w")))
|
|
return false;
|
|
fprintf(F, "%d", getpid());
|
|
fclose(F);
|
|
return true;
|
|
}
|