/*- * Copyright (c) 1999,2001 Andrzej Bialecki * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: spy.c,v 1.2 2001/07/10 22:33:06 root Exp root $ */ static const char cpy[] = "Copyright (c) 1999,2001 by Andrzej Bialecki "; static const char ver[] = "SPY 1.1"; #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __i386__ #include #elif __alpha__ /* XXX Should be installed in standard place, right? */ #include #else #error Unsupported architecture. Please report to #endif #include "spy.h" struct spy_ent spy_vec[SYS_MAXSYSCALL]; static u_int root_opt = (SPY_ROOT | SPY_ARGS); static u_int user_opt = (SPY_USER | SPY_ARGS); static u_int def_opt = (SPY_ALL | SPY_ARGS); static uid_t def_uid = 0; static gid_t def_gid = 0; static char buf[128]; SYSCTL_NODE(_kern, OID_AUTO, spy, CTLFLAG_RW, 0, "SPY module"); void format_opt(char *p, u_int * opt, uid_t * uid, gid_t * gid) { int i, first = 1; char *s; u_int ix; for (i = 0; i < SPY_OPTNUM; i++) { ix = (1 << i); if (*opt & ix) { if (first) { strcat(p, spy_opt[i]); first = 0; } else { strcat(p, ","); strcat(p, spy_opt[i]); } if ((ix & SPY_UID) && uid) { s = p + strlen(p); sprintf(s, "=%d", *uid); } if ((ix & SPY_GID) && gid) { s = p + strlen(p); sprintf(s, "=%d", *gid); } } } } u_int parse_opt(char *p, uid_t * uid, gid_t * gid) { u_int res = 0, ix; char *s, *t; int i; long val = 0; s = index(p, ','); if (s != NULL) { while (s != NULL) { *s = '\0'; s++; t = index(p, '='); if (t != NULL) { *t = '\0'; t++; val = strtol(t, NULL, 0); } for (i = 0; i < SPY_OPTNUM; i++) { if (strcmp(p, spy_opt[i]) == NULL) { ix = (1 << i); res |= ix; if ((ix & SPY_UID) && uid) { *uid = (uid_t) val; } if ((ix & SPY_GID) && gid) { *gid = (gid_t) val; } break; } } p = s; s = index(p, ','); } } t = index(p, '='); if (t != NULL) { *t = '\0'; t++; val = strtol(t, NULL, 0); } for (i = 0; i < SPY_OPTNUM; i++) { if (strcmp(p, spy_opt[i]) == NULL) { ix = (1 << i); res |= ix; if ((ix & SPY_UID) && uid) { *uid = (uid_t) val; } if ((ix & SPY_GID) && gid) { *gid = (gid_t) val; } break; } } return res; } int parse_sysc_opt(char *p, u_int * opt, uid_t * u, gid_t * g) { int res = 0; char *s; int i, found = 0; *opt = 0; if ((s = index(p, ',')) != NULL) { *s = '\0'; s++; *opt = parse_opt(s, u, g); } for (i = 0; i < SYS_MAXSYSCALL; i++) { if (strcmp(buf, syscallnames[i]) == NULL) { found++; res = i; break; } } if (!found) res = -1; return (res); } static int spy_sysctl_defopt(SYSCTL_HANDLER_ARGS) { int error = 0; u_int newopt; if (req->newptr) { error = sysctl_handle_string(oidp, buf, sizeof(buf), req); newopt = parse_opt(buf, &def_uid, &def_gid); def_opt = newopt; } buf[0] = '\0'; format_opt(buf, &def_opt, &def_uid, &def_gid); return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } SYSCTL_PROC(_kern_spy, OID_AUTO, defopt, CTLTYPE_STRING | CTLFLAG_RW, 0, 0, spy_sysctl_defopt, "A", "Default spy options"); static int spy_sysctl_list(SYSCTL_HANDLER_ARGS) { int error = 0, i; u_int opt; sprintf(buf, "\n SYSCALL USED OPTIONS"); error = sysctl_handle_opaque(oidp, buf, strlen(buf), req); for (i = 0; i < SYS_MAXSYSCALL; i++) { if (spy_vec[i].inuse) { if (spy_vec[i].opt == 0) { opt = def_opt; } else opt = spy_vec[i].opt; sprintf(buf, "\n %-15s %10llu ", syscallnames[i], spy_vec[i].used); format_opt(buf, &opt, &spy_vec[i].uid, &spy_vec[i].gid); if (spy_vec[i].block) strcat(buf, " DISABLED!"); error = sysctl_handle_opaque(oidp, buf, strlen(buf), req); if (error) return (error); } } /* end the string */ buf[0] = '\0'; error = sysctl_handle_opaque(oidp, buf, 1, req); return (error); } SYSCTL_PROC(_kern_spy, OID_AUTO, list, CTLTYPE_STRING | CTLFLAG_RD, 0, 0, spy_sysctl_list, "A", "List of controlled syscalls"); static int spy_sysctl_dump(SYSCTL_HANDLER_ARGS) { int error = 0, i; for (i = 0; i < SYS_MAXSYSCALL; i++) { if (spy_vec[i].inuse) { error = sysctl_handle_opaque(oidp, &spy_vec[i], sizeof(struct spy_ent), req); if (error) return (error); } } return (error); } SYSCTL_PROC(_kern_spy, OID_AUTO, dump, CTLTYPE_OPAQUE | CTLFLAG_RD, 0, 0, spy_sysctl_dump, "S,spy_ent", "Dump list of controlled syscalls"); static int spy_sysctl_add(SYSCTL_HANDLER_ARGS) { int error = 0, res; u_int opt; uid_t u; gid_t g; buf[0] = '\0'; if (req->newptr) { error = sysctl_handle_string(oidp, buf, sizeof(buf), req); res = parse_sysc_opt(buf, &opt, &u, &g); if (res != -1) { if (opt != 0) { spy_vec[res].opt = opt; spy_vec[res].uid = u; spy_vec[res].uid = u; } if (spy_vec[res].inuse == 0) { start_spy(res); log(LOG_SECURITY | LOG_INFO, "SPY adding %s to the list\n", syscallnames[res]); } } else return (EINVAL); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } SYSCTL_PROC(_kern_spy, OID_AUTO, add, CTLTYPE_STRING | CTLFLAG_RW, 0, 0, spy_sysctl_add, "A", "Add syscall to the list"); static int spy_sysctl_del(SYSCTL_HANDLER_ARGS) { int error = 0, res; u_int opt; uid_t u; gid_t g; buf[0] = '\0'; if (req->newptr) { error = sysctl_handle_string(oidp, buf, sizeof(buf), req); res = parse_sysc_opt(buf, &opt, &u, &g); if (res != -1) { if (opt != 0) { spy_vec[res].opt = opt; spy_vec[res].uid = u; spy_vec[res].uid = u; } if (spy_vec[res].inuse != 0) { stop_spy(res); log(LOG_SECURITY | LOG_INFO, "SPY removing %s from the list\n", syscallnames[res]); } } else return (EINVAL); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } SYSCTL_PROC(_kern_spy, OID_AUTO, del, CTLTYPE_STRING | CTLFLAG_RW, 0, 0, spy_sysctl_del, "A", "Remove syscall from the list"); static int spy_sysctl_disable(SYSCTL_HANDLER_ARGS) { int error = 0, res; u_int opt; uid_t u; gid_t g; buf[0] = '\0'; if (req->newptr) { error = sysctl_handle_string(oidp, buf, sizeof(buf), req); res = parse_sysc_opt(buf, &opt, &u, &g); if (res != -1) { if (opt != 0) { spy_vec[res].opt = opt; spy_vec[res].uid = u; spy_vec[res].uid = u; } if (spy_vec[res].block == 0) { sysc_disable(res); log(LOG_SECURITY | LOG_INFO, "SPY DISABLING %s!\n", syscallnames[res]); } } else return (EINVAL); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } SYSCTL_PROC(_kern_spy, OID_AUTO, disable, CTLTYPE_STRING | CTLFLAG_RW, 0, 0, spy_sysctl_disable, "A", "Disable syscall (BEWARE!)"); static int spy_sysctl_enable(SYSCTL_HANDLER_ARGS) { int error = 0, res; u_int opt; uid_t u; gid_t g; buf[0] = '\0'; if (req->newptr) { error = sysctl_handle_string(oidp, buf, sizeof(buf), req); res = parse_sysc_opt(buf, &opt, &u, &g); if (res != -1) { if (opt != 0) { spy_vec[res].opt = opt; spy_vec[res].uid = u; spy_vec[res].uid = u; } if (spy_vec[res].block != 0) { sysc_enable(res); log(LOG_SECURITY | LOG_INFO, "SPY ENABLING %s!\n", syscallnames[res]); } } else return (EINVAL); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } SYSCTL_PROC(_kern_spy, OID_AUTO, enable, CTLTYPE_STRING | CTLFLAG_RW, 0, 0, spy_sysctl_enable, "A", "Enable disabled syscall"); static int spy_sysctl_set(SYSCTL_HANDLER_ARGS) { int error = 0, res; u_int opt; uid_t u; gid_t g; buf[0] = '\0'; if (req->newptr) { error = sysctl_handle_string(oidp, buf, sizeof(buf), req); res = parse_sysc_opt(buf, &opt, &u, &g); if (res != -1) { if (opt != 0) { spy_vec[res].opt = opt; spy_vec[res].uid = u; spy_vec[res].uid = u; } buf[0] = '\0'; format_opt(buf, &opt, &spy_vec[res].uid, &spy_vec[res].gid); log(LOG_SECURITY | LOG_INFO, "SPY set %s, opt: %s\n", syscallnames[res], buf); } else return (EINVAL); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } SYSCTL_PROC(_kern_spy, OID_AUTO, set, CTLTYPE_STRING | CTLFLAG_RW, 0, 0, spy_sysctl_set, "A", "Set options of monitored syscall"); /* Initialize spy_vec with reasonable defaults. */ static void init_spy_vec(void) { int i; for (i = 0; i < SYS_MAXSYSCALL; i++) { spy_vec[i].o = sysent[i]; spy_vec[i].n = sysent[i]; spy_vec[i].n.sy_call = spy_dispatch; spy_vec[i].opt = 0; spy_vec[i].uid = 0; spy_vec[i].gid = 0; spy_vec[i].inuse = 0; spy_vec[i].block = 0; } } static void set_spy(u_int64_t sysc, void *f, u_int32_t opt) { spy_vec[sysc].handler = f; spy_vec[sysc].opt = opt; } static void start_spy(u_int64_t sysc) { sysent[sysc] = spy_vec[sysc].n; spy_vec[sysc].inuse++; } static void stop_spy(u_int64_t sysc) { sysent[sysc] = spy_vec[sysc].o; spy_vec[sysc].inuse = 0; } /* * Disable a syscall. Turn on monitoring as well. * * BEWARE: this is highly dangerous operation. It can leave your system in * totally unusable state, requiring perhaps cold reboot. */ static void sysc_disable(u_int64_t sysc) { start_spy(sysc); spy_vec[sysc].block++; } /* * Enable previously disabled syscall. You need to explicitly call stop_spy() * in order to stop monitoring this syscall. */ static void sysc_enable(u_int64_t sysc) { spy_vec[sysc].block = 0; } /* Handle module events. Currently only load and unload */ static int spy_modevent(module_t mod, int cmd, void *arg) { int err = 0; int i; switch (cmd) { case MOD_LOAD: init_spy_vec(); set_spy(SYS_execve, spy_execve, root_opt); set_spy(SYS_chdir, spy_chdir, root_opt); set_spy(SYS_setuid, spy_setuid, user_opt); set_spy(SYS_seteuid, spy_setuid, user_opt); set_spy(SYS_setreuid, spy_setuid, user_opt); set_spy(SYS_setgid, spy_setgid, user_opt); set_spy(SYS_setegid, spy_setgid, user_opt); set_spy(SYS_setregid, spy_setgid, user_opt); set_spy(SYS_open, spy_open, root_opt); set_spy(SYS_link, spy_link, root_opt); set_spy(SYS_unlink, spy_unlink, root_opt); set_spy(SYS_rmdir, spy_unlink, root_opt); set_spy(SYS_mkdir, spy_chmod, root_opt); set_spy(SYS_chmod, spy_chmod, root_opt); set_spy(SYS_chown, spy_chown, root_opt); set_spy(SYS_mount, spy_mount, root_opt); set_spy(SYS_unmount, spy_unlink, root_opt); /* By default start spying on execve and set[ug]id */ start_spy(SYS_execve); start_spy(SYS_setuid); start_spy(SYS_seteuid); start_spy(SYS_setreuid); start_spy(SYS_setgid); start_spy(SYS_setegid); start_spy(SYS_setregid); log(LOG_SECURITY | LOG_INFO, "%s %s\n", ver, cpy); break; case MOD_UNLOAD: for (i = 0; i < SYS_MAXSYSCALL; i++) { if (spy_vec[i].inuse) { stop_spy(i); } } log(LOG_SECURITY | LOG_INFO, "%s unloaded.\n", ver); break; default: /* we only understand load/unload */ err = EINVAL; break; } return (err); } /*********************************************************************** * Various helpers. ***********************************************************************/ /* Find out what syscall number is being executed. A bit tricky... */ static u_int64_t get_sysnum(struct proc * p) { u_int64_t res = 0L; #ifdef __i386__ res = p->p_frame->tf_eax; #elif __alpha__ /* XXX Someone please check this on Alpha... */ res = p->p_frame->tf_regs[FRAME_V0]; #endif return (res); } /* * Find out what mode of operation is for this spy_ent. Take into account * def_opt as well as specific override in this spy_ent. */ static u_int32_t get_opt(u_int64_t sysc) { if (spy_vec[sysc].opt == 0) { return (def_opt); } else { return (spy_vec[sysc].opt); } } /* Check if we should log this syscall, based on opt and credentials. */ static int skip(u_int64_t sysc, struct proc * p) { u_int opt = get_opt(sysc); int res = -1; if (opt & SPY_USER) { if (p->p_ucred->cr_ruid != 0) res = 0; } if (opt & SPY_UID) { if (p->p_ucred->cr_ruid == spy_vec[sysc].uid) res = 0; } if (opt & SPY_GID) { if (p->p_ucred->cr_rgid == spy_vec[sysc].gid) res = 0; } if (opt & SPY_ROOT) { if (p->p_ucred->cr_ruid == 0) res = 0; } /* update statistics */ spy_vec[sysc].used++; return (res); } /* Check if we should disable this syscall, based on flags and credentials */ static int is_disabled(u_int64_t sysc, struct proc * p) { u_int opt = get_opt(sysc); int res = 0; if (spy_vec[sysc].block == 0) return (0); if (opt & SPY_USER) { if (p->p_ucred->cr_ruid != 0) res++; } if (opt & SPY_UID) { if (p->p_ucred->cr_ruid == spy_vec[sysc].uid) res++; } if (opt & SPY_GID) { if (p->p_ucred->cr_rgid == spy_vec[sysc].gid) res++; } if (opt & SPY_ROOT) { if (p->p_ucred->cr_ruid == 0) res++; } return (res); } /* Make a generic log entry. */ static void generic_log(u_int64_t sysc, struct proc * p) { log(LOG_SECURITY | LOG_INFO, "SPY %s %u(%s):%u(%s) %u:%u %u:%u%s", syscallnames[sysc], p->p_pid, p->p_comm, p->p_pptr->p_pid, p->p_pptr->p_comm, p->p_ucred->cr_ruid, p->p_ucred->cr_rgid, p->p_ucred->cr_uid, p->p_ucred->cr_gid, is_disabled(sysc, p) ? " (DISABLED!)" : ""); } static int syscall_orig(u_int64_t sysc, struct proc * p, void *arg) { if (is_disabled(sysc, p)) { return (EPERM); } else { return (spy_vec[sysc].o.sy_call(p, arg)); } } /*********************************************************************** * Handlers for specific syscalls. * NOTE: in cases when there is no specific handler, you can always use * generic one, which will log only the basic information, but doesn't * handle syscall arguments. ***********************************************************************/ /* * Syscall dispatch handler. If the spy_vec entry contains specific handler * then it is called to log the arguments, otherwise it logs what it can... */ static int spy_dispatch(struct proc * p, void *arg) { u_int64_t sysnum; u_int32_t opt; sysnum = get_sysnum(p); opt = get_opt(sysnum); if (!skip(sysnum, p)) { generic_log(sysnum, p); if (spy_vec[sysnum].handler != NULL) { spy_vec[sysnum].handler(p, arg, opt); } else { if (opt & SPY_ARGS) { log(-1, " arg=%p", arg); } if (opt & SPY_OTHER) { log(-1, " (generic)"); } log(-1, "\n"); } } return (syscall_orig(sysnum, p, arg)); } /* SYS_execve args handler. */ static void spy_execve(struct proc * p, void *arg, u_int32_t opt) { struct execve_args *uap; int i; uap = (struct execve_args *) arg; log(-1, " cmd='%s", uap->argv[0]); if (opt & SPY_ARGS) { i = 1; while (uap->argv[i] != NULL) { log(-1, " %s", uap->argv[i]); i++; } } log(-1, "'\n"); if (opt & SPY_OTHER) { i = 0; while (uap->envv[i] != NULL) { log(LOG_SECURITY | LOG_INFO, "* env[%d]='%s'\n", i, uap->envv[i]); i++; } } } /* SYS_chdir args handler. */ static void spy_chdir(struct proc * p, void *arg, u_int32_t opt) { struct chdir_args *uap; uap = (struct chdir_args *) arg; if (opt & SPY_ARGS) { log(-1, " path='%s'", uap->path); } log(-1, "\n"); } /* SYS_set*uid args handler. */ static void spy_setuid(struct proc * p, void *arg, u_int32_t opt) { struct setuid_args *uap; uap = (struct setuid_args *) arg; if (opt & SPY_ARGS) { log(-1, " uid=%u", uap->uid); } log(-1, "\n"); } /* SYS_set*gid args handler. */ static void spy_setgid(struct proc * p, void *arg, u_int32_t opt) { struct setgid_args *uap; uap = (struct setgid_args *) arg; if (opt & SPY_ARGS) { log(-1, " gid=%u", uap->gid); } log(-1, "\n"); } /* SYS_open args handler. */ static void spy_open(struct proc * p, void *arg, u_int32_t opt) { struct open_args *uap; uap = (struct open_args *) arg; if (opt & SPY_ARGS) { log(-1, " path='%s' flags=%#x mode=%#x", uap->path, uap->flags, uap->mode); } log(-1, "\n"); } /* SYS_link args handler. */ static void spy_link(struct proc * p, void *arg, u_int32_t opt) { struct link_args *uap; uap = (struct link_args *) arg; if (opt & SPY_ARGS) { log(-1, " path='%s' link='%s'", uap->path, uap->link); } log(-1, "\n"); } /* SYS_unlink, SYS_rmdir and SYS_umount args handler. */ static void spy_unlink(struct proc * p, void *arg, u_int32_t opt) { struct unlink_args *uap; uap = (struct unlink_args *) arg; if (opt & SPY_ARGS) { log(-1, " path='%s'", uap->path); } log(-1, "\n"); } /* SYS_chmod and SYS_mkdir args handler. */ static void spy_chmod(struct proc * p, void *arg, u_int32_t opt) { struct chmod_args *uap; uap = (struct chmod_args *) arg; if (opt & SPY_ARGS) { log(-1, " path='%s', mode=%o", uap->path, uap->mode); } log(-1, "\n"); } /* SYS_chown args handler. */ static void spy_chown(struct proc * p, void *arg, u_int32_t opt) { struct chown_args *uap; uap = (struct chown_args *) arg; if (opt & SPY_ARGS) { log(-1, " path='%s', uid:gid=%u:%u", uap->path, uap->uid, uap->gid); } log(-1, "\n"); } /* SYS_mount args handler. */ static void spy_mount(struct proc * p, void *arg, u_int32_t opt) { struct mount_args *uap; uap = (struct mount_args *) arg; if (opt & SPY_ARGS) { log(-1, " type='%s' path='%s'", uap->type, uap->path); } log(-1, "\n"); } /* Now declare the module to the system */ static moduledata_t spy_mod_data = { "spy", spy_modevent, 0 }; DECLARE_MODULE(spy, spy_mod_data, SI_SUB_EXEC, SI_ORDER_ANY);