diff -urNp current/TODO hrl/TODO --- current/TODO 1970-01-01 01:00:00.000000000 +0100 +++ hrl/TODO 2009-12-18 11:19:12.948890045 +0100 @@ -0,0 +1,36 @@ + + - Make the limits lists protected by the subjects lock (e.g. process lock) + instead of hrl_lock. + + - Bring back per-group limits. + + - Some things need to be accounted for per-euid, and some per-egid. Geez. + + - In maxproc limit, make sure the 'p' argument is a child process. Otherwise, + if one adds rule with 'sig*' action, the signal will be sent to the parent + instead of the child. + + - Add sorting to hrl(8). + + - Use expand_number(3) in hrl(8). + + +Limits to do: + + - HRL_RESOURCE_CPUTIME + - HRL_RESOURCE_STACKSIZE + - HRL_RESOURCE_COREDUMPSIZE + - HRL_RESOURCE_MEMORYUSE + - HRL_RESOURCE_MEMORYLOCKED + - HRL_RESOURCE_SBSIZE + - HRL_RESOURCE_VMEMORYUSE + + +Limits done: + + - HRL_RESOURCE_OPENFILES + - HRL_RESOURCE_DATASIZE + - HRL_RESOURCE_FILESIZE + - HRL_RESOURCE_MAXPROCESSES + - HRL_RESOURCE_PTY + diff -urNp current/etc/rc.d/Makefile hrl/etc/rc.d/Makefile --- current/etc/rc.d/Makefile 2009-12-18 11:13:52.281097910 +0100 +++ hrl/etc/rc.d/Makefile 2009-12-18 11:24:18.721126182 +0100 @@ -13,7 +13,7 @@ FILES= DAEMON FILESYSTEMS LOGIN NETWORKI faith fsck ftp-proxy ftpd \ gbde geli geli2 gssd \ hcsecd \ - hostapd hostid hostid_save hostname \ + hostapd hostid hostid_save hostname hrl \ inetd initrandom \ ip6addrctl ipfilter ipfs ipfw ipmon \ ipnat ipsec ipxrouted \ diff -urNp current/etc/rc.d/hrl hrl/etc/rc.d/hrl --- current/etc/rc.d/hrl 1970-01-01 01:00:00.000000000 +0100 +++ hrl/etc/rc.d/hrl 2009-12-18 11:24:18.882629669 +0100 @@ -0,0 +1,39 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# PROVIDE: hrl +# BEFORE: LOGIN +# KEYWORD: nojail + +. /etc/rc.subr + +name="hrl" +start_cmd="hrl_start" +stop_cmd="hrl_stop" + +hrl_start() +{ + if [ -f /etc/hrl.conf ]; then + while read var comments + do + case ${var} in + \#*|'') + ;; + *) + hrl -a "${var}" + ;; + esac + done < /etc/hrl.conf + fi +} + +hrl_stop() +{ + + hrl -r :: +} + +load_rc_config $name +run_rc_command "$1" diff -urNp current/include/unistd.h hrl/include/unistd.h --- current/include/unistd.h 2009-12-18 11:14:23.507322751 +0100 +++ hrl/include/unistd.h 2009-12-18 11:24:31.961146911 +0100 @@ -503,6 +503,7 @@ int feature_present(const char *); char *fflagstostr(u_long); int getdomainname(char *, int); int getgrouplist(const char *, gid_t, gid_t *, int *); +int getloginclass(char *, size_t); mode_t getmode(const void *, mode_t); int getosreldate(void); int getpeereid(int, uid_t *, gid_t *); @@ -562,6 +563,7 @@ int setkey(const char *); #define _SETKEY_DECLARED #endif int setlogin(const char *); +int setloginclass(const char *); void *setmode(const char *); void setproctitle(const char *_fmt, ...) __printf0like(1, 2); int setresgid(gid_t, gid_t, gid_t); diff -urNp current/lib/libc/sys/Symbol.map hrl/lib/libc/sys/Symbol.map --- current/lib/libc/sys/Symbol.map 2009-12-18 11:14:59.076449833 +0100 +++ hrl/lib/libc/sys/Symbol.map 2009-12-18 11:24:42.443742883 +0100 @@ -342,6 +342,7 @@ FBSD_1.1 { fexecve; fstatat; futimesat; + getloginclass; jail_get; jail_set; jail_remove; @@ -355,9 +356,15 @@ FBSD_1.1 { readlinkat; renameat; setfib; + setloginclass; shmctl; symlinkat; unlinkat; + hrl_get_usage; + hrl_get_rules; + hrl_get_limits; + hrl_add_rule; + hrl_remove_rule; }; FBSDprivate_1.0 { diff -urNp current/lib/libutil/login_cap.h hrl/lib/libutil/login_cap.h --- current/lib/libutil/login_cap.h 2009-12-18 11:15:22.334887428 +0100 +++ hrl/lib/libutil/login_cap.h 2009-12-18 11:24:50.805842815 +0100 @@ -49,7 +49,8 @@ #define LOGIN_SETENV 0x0080 /* set user environment */ #define LOGIN_SETMAC 0x0100 /* set user default MAC label */ #define LOGIN_SETCPUMASK 0x0200 /* set user cpumask */ -#define LOGIN_SETALL 0x03ff /* set everything */ +#define LOGIN_SETLOGINCLASS 0x0400 /* set login class in the kernel */ +#define LOGIN_SETALL 0x07ff /* set everything */ #define BI_AUTH "authorize" /* accepted authentication */ #define BI_REJECT "reject" /* rejected authentication */ diff -urNp current/lib/libutil/login_class.c hrl/lib/libutil/login_class.c --- current/lib/libutil/login_class.c 2009-12-18 11:15:22.365201337 +0100 +++ hrl/lib/libutil/login_class.c 2009-12-18 11:24:50.805842815 +0100 @@ -512,6 +512,16 @@ setusercontext(login_cap_t *lc, const st return (-1); } + if (lc != NULL && lc->lc_class != NULL) { + /* Inform the kernel about current login class */ + if ((flags & LOGIN_SETLOGINCLASS) && + setloginclass(lc->lc_class) != 0) { + syslog(LOG_ERR, "setloginclass(%s): %m", lc->lc_class); + login_close(llc); + return (-1); + } + } + mymask = (flags & LOGIN_SETUMASK) ? umask(LOGIN_DEFUMASK) : 0; mymask = setlogincontext(lc, pwd, mymask, flags); login_close(llc); diff -urNp current/sys/compat/freebsd32/syscalls.master hrl/sys/compat/freebsd32/syscalls.master --- current/sys/compat/freebsd32/syscalls.master 2009-12-18 11:17:01.003479945 +0100 +++ hrl/sys/compat/freebsd32/syscalls.master 2009-12-18 11:25:55.319341877 +0100 @@ -917,3 +917,11 @@ fd_set *ou, fd_set *ex, \ const struct timespec32 *ts, \ const sigset_t *sm); } +523 AUE_NULL NOPROTO { int getloginclass(char *namebuf, size_t \ + namelen); } +524 AUE_NULL NOPROTO { int setloginclass(const char *namebuf); } +525 AUE_NULL NOPROTO { int hrl_get_usage(const void *inbufp, size_t inbuflen, void *outbufp, size_t outbuflen); } +526 AUE_NULL NOPROTO { int hrl_get_rules(const void *inbufp, size_t inbuflen, void *outbufp, size_t outbuflen); } +527 AUE_NULL NOPROTO { int hrl_get_limits(const void *inbufp, size_t inbuflen, void *outbufp, size_t outbuflen); } +528 AUE_NULL NOPROTO { int hrl_add_rule(const void *inbufp, size_t inbuflen, void *outbufp, size_t outbuflen); } +529 AUE_NULL NOPROTO { int hrl_remove_rule(const void *inbufp, size_t inbuflen, void *outbufp, size_t outbuflen); } diff -urNp current/sys/compat/linux/linux_misc.c hrl/sys/compat/linux/linux_misc.c --- current/sys/compat/linux/linux_misc.c 2009-12-18 11:17:01.286347181 +0100 +++ hrl/sys/compat/linux/linux_misc.c 2009-12-18 11:25:55.955492561 +0100 @@ -35,6 +35,7 @@ __FBSDID("$FreeBSD: src/sys/compat/linux #include #include #include +#include #if defined(__i386__) #include #endif @@ -358,14 +359,16 @@ linux_uselib(struct thread *td, struct l * XXX - this is not complete. it should check current usage PLUS * the resources needed by this library. */ - PROC_LOCK(td->td_proc); - if (a_out->a_text > maxtsiz || - a_out->a_data + bss_size > lim_cur(td->td_proc, RLIMIT_DATA)) { - PROC_UNLOCK(td->td_proc); + if (a_out->a_text > maxtsiz) { + error = ENOMEM; + goto cleanup; + } + error = hrl_allocated(td->td_proc, HRL_RESOURCE_DATASIZE, + a_out->a_data + bss_size); + if (error) { error = ENOMEM; goto cleanup; } - PROC_UNLOCK(td->td_proc); /* * Prevent more writers. @@ -452,8 +455,10 @@ linux_uselib(struct thread *td, struct l /* allocate some 'anon' space */ error = vm_map_find(&td->td_proc->p_vmspace->vm_map, NULL, 0, &vmaddr, bss_size, FALSE, VM_PROT_ALL, VM_PROT_ALL, 0); - if (error) + if (error) { + error = ENOMEM; goto cleanup; + } } cleanup: diff -urNp current/sys/compat/svr4/imgact_svr4.c hrl/sys/compat/svr4/imgact_svr4.c --- current/sys/compat/svr4/imgact_svr4.c 2009-12-18 11:17:01.872023572 +0100 +++ hrl/sys/compat/svr4/imgact_svr4.c 2009-12-18 11:25:56.935290552 +0100 @@ -36,6 +36,7 @@ __FBSDID("$FreeBSD: src/sys/compat/svr4/ #include #include #include +#include #include #include #include @@ -106,13 +107,12 @@ exec_svr4_imgact(imgp) /* * text/data/bss must not exceed limits */ - PROC_LOCK(imgp->proc); - if (a_out->a_text > maxtsiz || - a_out->a_data + bss_size > lim_cur(imgp->proc, RLIMIT_DATA)) { - PROC_UNLOCK(imgp->proc); - return (ENOMEM); - } - PROC_UNLOCK(imgp->proc); + if (a_out->a_text > maxtsiz) + return (ENOMEM); + error = hrl_allocated(imgp->proc, HRL_RESOURCE_DATASIZE, + a_out->a_data + bss_size); + if (error) + return (ENOMEM); VOP_UNLOCK(imgp->vp, 0); diff -urNp current/sys/compat/svr4/svr4_filio.c hrl/sys/compat/svr4/svr4_filio.c --- current/sys/compat/svr4/svr4_filio.c 2009-12-18 11:17:01.892235638 +0100 +++ hrl/sys/compat/svr4/svr4_filio.c 2009-12-18 11:25:57.046334274 +0100 @@ -37,6 +37,7 @@ __FBSDID("$FreeBSD: src/sys/compat/svr4/ #include #include #include +#include #include #include #include @@ -66,6 +67,9 @@ svr4_sys_poll(td, uap) int idx = 0, cerr; u_long siz; + /* + * XXX: What is this for? + */ PROC_LOCK(td->td_proc); if (uap->nfds > lim_cur(td->td_proc, RLIMIT_NOFILE) && uap->nfds > FD_SETSIZE) { @@ -74,6 +78,8 @@ svr4_sys_poll(td, uap) } PROC_UNLOCK(td->td_proc); + hrl_alloc(td->td_proc, HRL_RESOURCE_FILEDESCRIPTORS, uap->nfds); + pa.fds = uap->fds; pa.nfds = uap->nfds; pa.timeout = uap->timeout; @@ -99,6 +105,7 @@ svr4_sys_poll(td, uap) forget to update it if I add more code */ } done: + hrl_free(td->td_proc, HRL_RESOURCE_FILEDESCRIPTORS, uap->nfds); free(pfd, M_TEMP); return error; } diff -urNp current/sys/conf/files hrl/sys/conf/files --- current/sys/conf/files 2009-12-18 11:17:02.376953109 +0100 +++ hrl/sys/conf/files 2009-12-18 11:25:57.934989396 +0100 @@ -1988,6 +1988,7 @@ kern/kern_exec.c standard kern/kern_exit.c standard kern/kern_fail.c standard kern/kern_fork.c standard +kern/kern_hrl.c standard kern/kern_idle.c standard kern/kern_intr.c standard kern/kern_jail.c standard @@ -1998,6 +1999,7 @@ kern/kern_linker.c standard kern/kern_lock.c standard kern/kern_lockf.c standard kern/kern_lockstat.c optional kdtrace_hooks +kern/kern_loginclass.c standard kern/kern_malloc.c standard kern/kern_mbuf.c standard kern/kern_mib.c standard diff -urNp current/sys/fs/fdescfs/fdesc_vfsops.c hrl/sys/fs/fdescfs/fdesc_vfsops.c --- current/sys/fs/fdescfs/fdesc_vfsops.c 2009-12-18 11:17:42.531169358 +0100 +++ hrl/sys/fs/fdescfs/fdesc_vfsops.c 2009-12-18 11:27:55.690998330 +0100 @@ -198,6 +198,7 @@ fdesc_statfs(mp, sbp) PROC_LOCK(td->td_proc); lim = lim_cur(td->td_proc, RLIMIT_NOFILE); PROC_UNLOCK(td->td_proc); + /* XXX: Check HRL? */ fdp = td->td_proc->p_fd; FILEDESC_SLOCK(fdp); last = min(fdp->fd_nfiles, lim); diff -urNp current/sys/fs/msdosfs/msdosfs_vnops.c hrl/sys/fs/msdosfs/msdosfs_vnops.c --- current/sys/fs/msdosfs/msdosfs_vnops.c 2009-12-18 11:17:42.713121254 +0100 +++ hrl/sys/fs/msdosfs/msdosfs_vnops.c 2009-12-18 11:27:57.377630291 +0100 @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -700,14 +701,10 @@ msdosfs_write(ap) * If they've exceeded their filesize limit, tell them about it. */ if (td != NULL) { - PROC_LOCK(td->td_proc); - if ((uoff_t)uio->uio_offset + uio->uio_resid > - lim_cur(td->td_proc, RLIMIT_FSIZE)) { - psignal(td->td_proc, SIGXFSZ); - PROC_UNLOCK(td->td_proc); + error = hrl_allocated(td->td_proc, HRL_RESOURCE_FILESIZE, + (uoff_t)uio->uio_offset + uio->uio_resid); + if (error) return (EFBIG); - } - PROC_UNLOCK(td->td_proc); } /* diff -urNp current/sys/fs/nfsclient/nfs_clbio.c hrl/sys/fs/nfsclient/nfs_clbio.c --- current/sys/fs/nfsclient/nfs_clbio.c 2009-12-18 11:17:43.187712578 +0100 +++ hrl/sys/fs/nfsclient/nfs_clbio.c 2009-12-18 11:27:59.084367320 +0100 @@ -39,6 +39,7 @@ __FBSDID("$FreeBSD: src/sys/fs/nfsclient #include #include #include +#include #include #include #include @@ -1054,14 +1055,10 @@ flush_and_restart: * file servers have no limits, i don't think it matters */ if (p != NULL) { - PROC_LOCK(p); - if (uio->uio_offset + uio->uio_resid > - lim_cur(p, RLIMIT_FSIZE)) { - psignal(p, SIGXFSZ); - PROC_UNLOCK(p); + error = hrl_allocated(p, HRL_RESOURCE_FILESIZE, + (uoff_t)uio->uio_offset + uio->uio_resid); + if (error) return (EFBIG); - } - PROC_UNLOCK(p); } biosize = vp->v_mount->mnt_stat.f_iosize; diff -urNp current/sys/fs/nwfs/nwfs_io.c hrl/sys/fs/nwfs/nwfs_io.c --- current/sys/fs/nwfs/nwfs_io.c 2009-12-18 11:17:45.197347601 +0100 +++ hrl/sys/fs/nwfs/nwfs_io.c 2009-12-18 11:28:03.488429568 +0100 @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -236,14 +237,10 @@ nwfs_writevnode(vp, uiop, cred, ioflag) } if (uiop->uio_resid == 0) return 0; if (td != NULL) { - PROC_LOCK(td->td_proc); - if (uiop->uio_offset + uiop->uio_resid > - lim_cur(td->td_proc, RLIMIT_FSIZE)) { - psignal(td->td_proc, SIGXFSZ); - PROC_UNLOCK(td->td_proc); + error = hrl_allocated(td->td_proc, HRL_RESOURCE_FILESIZE, + (uoff_t)uiop->uio_offset + uiop->uio_resid); + if (error) return (EFBIG); - } - PROC_UNLOCK(td->td_proc); } error = ncp_write(NWFSTOCONN(nmp), &np->n_fh, uiop, cred); NCPVNDEBUG("after: ofs=%d,resid=%d\n",(int)uiop->uio_offset, uiop->uio_resid); diff -urNp current/sys/fs/smbfs/smbfs_io.c hrl/sys/fs/smbfs/smbfs_io.c --- current/sys/fs/smbfs/smbfs_io.c 2009-12-18 11:17:45.621973356 +0100 +++ hrl/sys/fs/smbfs/smbfs_io.c 2009-12-18 11:28:04.185298863 +0100 @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -278,14 +279,10 @@ smbfs_writevnode(struct vnode *vp, struc if (uiop->uio_resid == 0) return 0; if (p != NULL) { - PROC_LOCK(p); - if (uiop->uio_offset + uiop->uio_resid > - lim_cur(p, RLIMIT_FSIZE)) { - psignal(p, SIGXFSZ); - PROC_UNLOCK(p); - return EFBIG; - } - PROC_UNLOCK(p); + error = hrl_allocated(p, HRL_RESOURCE_FILESIZE, + (uoff_t)uiop->uio_offset + uiop->uio_resid); + if (error) + return (EFBIG); } smb_makescred(&scred, td, cred); error = smb_write(smp->sm_share, np->n_fid, uiop, &scred); diff -urNp current/sys/fs/tmpfs/tmpfs_vnops.c hrl/sys/fs/tmpfs/tmpfs_vnops.c --- current/sys/fs/tmpfs/tmpfs_vnops.c 2009-12-18 11:17:46.035670069 +0100 +++ hrl/sys/fs/tmpfs/tmpfs_vnops.c 2009-12-18 11:28:04.810516034 +0100 @@ -38,6 +38,7 @@ __FBSDID("$FreeBSD: src/sys/fs/tmpfs/tmp #include #include +#include #include #include #include @@ -723,14 +724,10 @@ tmpfs_write(struct vop_write_args *v) return (EFBIG); if (vp->v_type == VREG && td != NULL) { - PROC_LOCK(td->td_proc); - if (uio->uio_offset + uio->uio_resid > - lim_cur(td->td_proc, RLIMIT_FSIZE)) { - psignal(td->td_proc, SIGXFSZ); - PROC_UNLOCK(td->td_proc); + error = hrl_allocated(td->td_proc, HRL_RESOURCE_FILESIZE, + (uoff_t)uio->uio_offset + uio->uio_resid); + if (error) return (EFBIG); - } - PROC_UNLOCK(td->td_proc); } extended = uio->uio_offset + uio->uio_resid > node->tn_size; diff -urNp current/sys/fs/unionfs/union_subr.c hrl/sys/fs/unionfs/union_subr.c --- current/sys/fs/unionfs/union_subr.c 2009-12-18 11:17:46.409291018 +0100 +++ hrl/sys/fs/unionfs/union_subr.c 2009-12-18 11:28:05.204584554 +0100 @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -776,6 +777,10 @@ unionfs_mkshadowdir(struct unionfs_mount rootinfo = uifind((uid_t)0); cred = crdup(cnp->cn_cred); chgproccnt(cred->cr_ruidinfo, 1, 0); +#ifdef notyet + /* XXX: What about the return value? And what's the purpose of this, anyway? */ + hrl_alloc(HRL_RESOURCE_MAXPROCESSES, 1); +#endif change_euid(cred, rootinfo); change_ruid(cred, rootinfo); change_svuid(cred, (uid_t)0); @@ -826,6 +831,9 @@ unionfs_mkshadowdir_free_out: unionfs_mkshadowdir_abort: cnp->cn_cred = credbk; chgproccnt(cred->cr_ruidinfo, -1, 0); +#ifdef notyet + hrl_free(HRL_RESOURCE_MAXPROCESSES, 1); +#endif crfree(cred); return (error); diff -urNp current/sys/gnu/fs/ext2fs/ext2_readwrite.c hrl/sys/gnu/fs/ext2fs/ext2_readwrite.c --- current/sys/gnu/fs/ext2fs/ext2_readwrite.c 2009-12-18 11:17:50.671173464 +0100 +++ hrl/sys/gnu/fs/ext2fs/ext2_readwrite.c 2009-12-18 11:28:10.031987478 +0100 @@ -210,14 +210,10 @@ WRITE(ap) */ td = uio->uio_td; if (vp->v_type == VREG && td != NULL) { - PROC_LOCK(td->td_proc); - if (uio->uio_offset + uio->uio_resid > - lim_cur(td->td_proc, RLIMIT_FSIZE)) { - psignal(td->td_proc, SIGXFSZ); - PROC_UNLOCK(td->td_proc); + error = hrl_allocated(td->td_proc, HRL_RESOURCE_FILESIZE, + (uoff_t)uio->uio_offset + uio->uio_resid); + if (error) return (EFBIG); - } - PROC_UNLOCK(td->td_proc); } resid = uio->uio_resid; diff -urNp current/sys/gnu/fs/xfs/FreeBSD/xfs_vnops.c hrl/sys/gnu/fs/xfs/FreeBSD/xfs_vnops.c --- current/sys/gnu/fs/xfs/FreeBSD/xfs_vnops.c 2009-12-18 11:17:51.934081460 +0100 +++ hrl/sys/gnu/fs/xfs/FreeBSD/xfs_vnops.c 2009-12-18 11:28:11.566872499 +0100 @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -599,14 +600,10 @@ xfs_write_file(xfs_inode_t *xip, struct #if 0 td = uio->uio_td; if (vp->v_type == VREG && td != NULL) { - PROC_LOCK(td->td_proc); - if (uio->uio_offset + uio->uio_resid > - lim_cur(td->td_proc, RLIMIT_FSIZE)) { - psignal(td->td_proc, SIGXFSZ); - PROC_UNLOCK(td->td_proc); + error = hrl_allocated(td->td_proc, HRL_RESOURCE_FILESIZE, + (uoff_t)uio->uio_offset + uio->uio_resid); + if (error) return (EFBIG); - } - PROC_UNLOCK(td->td_proc); } #endif diff -urNp current/sys/i386/linux/imgact_linux.c hrl/sys/i386/linux/imgact_linux.c --- current/sys/i386/linux/imgact_linux.c 2009-12-18 11:17:58.124397547 +0100 +++ hrl/sys/i386/linux/imgact_linux.c 2009-12-18 11:28:27.038764317 +0100 @@ -105,13 +105,12 @@ exec_linux_imgact(struct image_params *i /* * text/data/bss must not exceed limits */ - PROC_LOCK(imgp->proc); - if (a_out->a_text > maxtsiz || - a_out->a_data + bss_size > lim_cur(imgp->proc, RLIMIT_DATA)) { - PROC_UNLOCK(imgp->proc); + if (a_out->a_text > maxtsiz) + return (ENOMEM); + error = hrl_allocated(imgp->proc, HRL_RESOURCE_DATASIZE, + a_out->a_data + bss_size); + if (error) return (ENOMEM); - } - PROC_UNLOCK(imgp->proc); VOP_UNLOCK(imgp->vp, 0); diff -urNp current/sys/kern/imgact_aout.c hrl/sys/kern/imgact_aout.c --- current/sys/kern/imgact_aout.c 2009-12-18 11:18:00.002739348 +0100 +++ hrl/sys/kern/imgact_aout.c 2009-12-18 11:28:29.987720120 +0100 @@ -29,6 +29,7 @@ __FBSDID("$FreeBSD: src/sys/kern/imgact_ #include #include +#include #include #include #include @@ -180,16 +181,13 @@ exec_aout_imgact(imgp) /* * text/data/bss must not exceed limits */ - PROC_LOCK(imgp->proc); if (/* text can't exceed maximum text size */ - a_out->a_text > maxtsiz || - - /* data + bss can't exceed rlimit */ - a_out->a_data + bss_size > lim_cur(imgp->proc, RLIMIT_DATA)) { - PROC_UNLOCK(imgp->proc); - return (ENOMEM); - } - PROC_UNLOCK(imgp->proc); + a_out->a_text > maxtsiz) + return (ENOMEM); + error = hrl_allocated(imgp->proc, HRL_RESOURCE_DATASIZE, + a_out->a_data + bss_size); + if (error) + return (ENOMEM); /* * Avoid a possible deadlock if the current address space is destroyed diff -urNp current/sys/kern/imgact_elf.c hrl/sys/kern/imgact_elf.c --- current/sys/kern/imgact_elf.c 2009-12-18 11:18:00.033271720 +0100 +++ hrl/sys/kern/imgact_elf.c 2009-12-18 11:28:30.039167721 +0100 @@ -35,6 +35,7 @@ __FBSDID("$FreeBSD: src/sys/kern/imgact_ #include #include +#include #include #include #include @@ -870,14 +871,18 @@ __CONCAT(exec_, __elfN(imgact))(struct i * limits after loading the segments since we do * not actually fault in all the segments pages. */ - PROC_LOCK(imgp->proc); - if (data_size > lim_cur(imgp->proc, RLIMIT_DATA) || - text_size > maxtsiz || - total_size > lim_cur(imgp->proc, RLIMIT_VMEM)) { - PROC_UNLOCK(imgp->proc); + if (text_size > maxtsiz) + return (ENOMEM); + error = hrl_allocated(imgp->proc, HRL_RESOURCE_DATASIZE, + data_size); + if (error) + return (ENOMEM); + error = hrl_allocated(imgp->proc, HRL_RESOURCE_VMEMORYUSE, + total_size); + if (error) return (ENOMEM); - } + PROC_LOCK(imgp->proc); vmspace->vm_tsize = text_size >> PAGE_SHIFT; vmspace->vm_taddr = (caddr_t)(uintptr_t)text_addr; vmspace->vm_dsize = data_size >> PAGE_SHIFT; diff -urNp current/sys/kern/imgact_gzip.c hrl/sys/kern/imgact_gzip.c --- current/sys/kern/imgact_gzip.c 2009-12-18 11:18:00.043193930 +0100 +++ hrl/sys/kern/imgact_gzip.c 2009-12-18 11:28:30.109112085 +0100 @@ -210,18 +210,15 @@ do_aout_hdr(struct imgact_gzip * gz) /* * text/data/bss must not exceed limits */ - PROC_LOCK(gz->ip->proc); if ( /* text can't exceed maximum text size */ - gz->a_out.a_text > maxtsiz || - - /* data + bss can't exceed rlimit */ - gz->a_out.a_data + gz->bss_size > - lim_cur(gz->ip->proc, RLIMIT_DATA)) { - PROC_UNLOCK(gz->ip->proc); + gz->a_out.a_text > maxtsiz) { gz->where = __LINE__; return (ENOMEM); } - PROC_UNLOCK(gz->ip->proc); + error = hrl_allocated_proc(gz->ip->proc, HRL_RESOURCE_DATASIZE, + gz->a_out.a_data + gz->bss_size); + if (error) + return (ENOMEM); /* Find out how far we should go */ gz->file_end = gz->file_offset + gz->a_out.a_text + gz->a_out.a_data; diff -urNp current/sys/kern/init_main.c hrl/sys/kern/init_main.c --- current/sys/kern/init_main.c 2009-12-18 11:18:00.083595714 +0100 +++ hrl/sys/kern/init_main.c 2009-12-18 11:28:30.209869025 +0100 @@ -52,9 +52,11 @@ __FBSDID("$FreeBSD: src/sys/kern/init_ma #include #include #include +#include #include #include #include +#include #include #include #include @@ -382,7 +384,7 @@ static void proc0_init(void *dummy __unused) { struct proc *p; - unsigned i; + unsigned i, error; struct thread *td; GIANT_REQUIRED; @@ -466,6 +468,7 @@ proc0_init(void *dummy __unused) p->p_ucred->cr_uidinfo = uifind(0); p->p_ucred->cr_ruidinfo = uifind(0); p->p_ucred->cr_prison = &prison0; + p->p_ucred->cr_loginclass = loginclass_find("default"); #ifdef AUDIT audit_cred_kproc0(p->p_ucred); #endif @@ -474,6 +477,9 @@ proc0_init(void *dummy __unused) #endif td->td_ucred = crhold(p->p_ucred); + /* Let the HRL know about the new process. */ + hrl_proc_init(p); + /* Create sigacts. */ p->p_sigacts = sigacts_alloc(); @@ -528,6 +534,8 @@ proc0_init(void *dummy __unused) * Charge root for one process. */ (void)chgproccnt(p->p_ucred->cr_ruidinfo, 1, 0); + error = hrl_alloc(p, HRL_RESOURCE_MAXPROCESSES, 1); + KASSERT(error == 0, ("hrl_alloc failed")); } SYSINIT(p0init, SI_SUB_INTRINSIC, SI_ORDER_FIRST, proc0_init, NULL); diff -urNp current/sys/kern/init_sysent.c hrl/sys/kern/init_sysent.c --- current/sys/kern/init_sysent.c 2009-12-18 11:18:00.164534771 +0100 +++ hrl/sys/kern/init_sysent.c 2009-12-18 11:28:30.260419863 +0100 @@ -2,8 +2,8 @@ * System call switch table. * * DO NOT EDIT-- this file is automatically generated. - * $FreeBSD: src/sys/kern/init_sysent.c,v 1.255 2009/10/27 11:01:15 kib Exp $ - * created from FreeBSD: head/sys/kern/syscalls.master 198508 2009-10-27 10:55:34Z kib + * $FreeBSD$ + * created from FreeBSD: src/sys/kern/syscalls.master,v 1.261 2009/10/27 10:55:34 kib Exp */ #include "opt_compat.h" @@ -557,4 +557,11 @@ struct sysent sysent[] = { { 0, (sy_call_t *)nosys, AUE_NULL, NULL, 0, 0, 0 }, /* 520 = pdgetpid */ { 0, (sy_call_t *)nosys, AUE_NULL, NULL, 0, 0, 0 }, /* 521 = pdwait */ { AS(pselect_args), (sy_call_t *)pselect, AUE_SELECT, NULL, 0, 0, 0 }, /* 522 = pselect */ + { AS(getloginclass_args), (sy_call_t *)getloginclass, AUE_NULL, NULL, 0, 0, 0 }, /* 523 = getloginclass */ + { AS(setloginclass_args), (sy_call_t *)setloginclass, AUE_NULL, NULL, 0, 0, 0 }, /* 524 = setloginclass */ + { AS(hrl_get_usage_args), (sy_call_t *)hrl_get_usage, AUE_NULL, NULL, 0, 0, 0 }, /* 525 = hrl_get_usage */ + { AS(hrl_get_rules_args), (sy_call_t *)hrl_get_rules, AUE_NULL, NULL, 0, 0, 0 }, /* 526 = hrl_get_rules */ + { AS(hrl_get_limits_args), (sy_call_t *)hrl_get_limits, AUE_NULL, NULL, 0, 0, 0 }, /* 527 = hrl_get_limits */ + { AS(hrl_add_rule_args), (sy_call_t *)hrl_add_rule, AUE_NULL, NULL, 0, 0, 0 }, /* 528 = hrl_add_rule */ + { AS(hrl_remove_rule_args), (sy_call_t *)hrl_remove_rule, AUE_NULL, NULL, 0, 0, 0 }, /* 529 = hrl_remove_rule */ }; diff -urNp current/sys/kern/kern_exec.c hrl/sys/kern/kern_exec.c --- current/sys/kern/kern_exec.c 2009-12-18 11:18:00.507660504 +0100 +++ hrl/sys/kern/kern_exec.c 2009-12-18 11:28:30.785520120 +0100 @@ -701,7 +701,7 @@ interpret: */ change_svuid(newcred, newcred->cr_uid); change_svgid(newcred, newcred->cr_gid); - p->p_ucred = newcred; + change_cred(p, newcred); newcred = NULL; } else { if (oldcred->cr_uid == oldcred->cr_ruid && @@ -723,7 +723,7 @@ interpret: oldcred->cr_svgid != oldcred->cr_gid) { change_svuid(newcred, newcred->cr_uid); change_svgid(newcred, newcred->cr_gid); - p->p_ucred = newcred; + change_cred(p, newcred); newcred = NULL; } } diff -urNp current/sys/kern/kern_exit.c hrl/sys/kern/kern_exit.c --- current/sys/kern/kern_exit.c 2009-12-18 11:18:00.517851185 +0100 +++ hrl/sys/kern/kern_exit.c 2009-12-18 11:28:30.836019555 +0100 @@ -66,6 +66,7 @@ __FBSDID("$FreeBSD: src/sys/kern/kern_ex #include #include /* for acct_process() function prototype */ #include +#include #include #include #include @@ -765,6 +766,8 @@ proc_reap(struct thread *td, struct proc * Decrement the count of procs running with this uid. */ (void)chgproccnt(p->p_ucred->cr_ruidinfo, -1, 0); + hrl_free(p->p_pptr, HRL_RESOURCE_MAXPROCESSES, 1); + hrl_proc_exiting(p); /* * Free credentials, arguments, and sigacts. @@ -925,6 +928,10 @@ proc_reparent(struct proc *child, struct if (child->p_pptr == parent) return; + hrl_free(child->p_pptr, HRL_RESOURCE_MAXPROCESSES, 1); + /* XXX: What about return value? */ + hrl_alloc(parent, HRL_RESOURCE_MAXPROCESSES, 1); + PROC_LOCK(child->p_pptr); sigqueue_take(child->p_ksi); PROC_UNLOCK(child->p_pptr); diff -urNp current/sys/kern/kern_fork.c hrl/sys/kern/kern_fork.c --- current/sys/kern/kern_fork.c 2009-12-18 11:18:00.568386378 +0100 +++ hrl/sys/kern/kern_fork.c 2009-12-18 11:28:30.947233690 +0100 @@ -46,6 +46,7 @@ __FBSDID("$FreeBSD: src/sys/kern/kern_fo #include #include #include +#include #include #include #include @@ -223,6 +224,10 @@ fork1(td, flags, pages, procp) p1 = td->td_proc; + error = hrl_alloc(p1, HRL_RESOURCE_MAXPROCESSES, 1); + if (error) + return (error); + /* * Here we don't create a new process, but we divorce * certain parts of a process from itself. @@ -233,6 +238,7 @@ fork1(td, flags, pages, procp) PROC_LOCK(p1); if (thread_single(SINGLE_BOUNDARY)) { PROC_UNLOCK(p1); + hrl_free(p1, HRL_RESOURCE_MAXPROCESSES, 1); return (ERESTART); } PROC_UNLOCK(p1); @@ -266,6 +272,8 @@ norfproc_fail: PROC_UNLOCK(p1); } *procp = NULL; + if (error) + hrl_free(p1, HRL_RESOURCE_MAXPROCESSES, 1); return (error); } @@ -798,6 +806,7 @@ fail1: vmspace_free(vm2); uma_zfree(proc_zone, newproc); pause("fork", hz / 2); + hrl_free(p1, HRL_RESOURCE_MAXPROCESSES, 1); return (error); } diff -urNp current/sys/kern/kern_hrl.c hrl/sys/kern/kern_hrl.c --- current/sys/kern/kern_hrl.c 1970-01-01 01:00:00.000000000 +0100 +++ hrl/sys/kern/kern_hrl.c 2009-12-18 11:28:30.998511436 +0100 @@ -0,0 +1,1933 @@ +/*- + * Copyright (c) 2009 Edward Tomasz Napierała + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HRF_DEFAULT 0 +#define HRF_DONT_INHERIT 1 +#define HRF_DONT_ACCUMULATE 2 + +/* Default buffer size for hrl_get_rules(2). */ +#define HRL_DEFAULT_BUFSIZE 4096 +#define HRL_LOG_BUFSIZE 128 + +/* + * 'hrl_limit' connects a rule with every container it's related to. + * For example, rule 'user:X:openfiles:deny=N/process' is linked + * with uidinfo for user X, and to each process of that user. + */ +struct hrl_limit { + LIST_ENTRY(hrl_limit) hl_next; + struct hrl_rule *hl_rule; +}; + +struct dict { + const char *d_name; + int d_value; +}; + +static struct dict subjectnames[] = { + { "process", HRL_SUBJECT_TYPE_PROCESS }, + { "user", HRL_SUBJECT_TYPE_USER }, + { "group", HRL_SUBJECT_TYPE_GROUP }, + { "loginclass", HRL_SUBJECT_TYPE_LOGINCLASS }, + { "jail", HRL_SUBJECT_TYPE_JAIL }, + { NULL, -1 }}; + +static struct dict resourcenames[] = { + { "cputime", HRL_RESOURCE_CPUTIME }, + { "filesize", HRL_RESOURCE_FILESIZE }, + { "datasize", HRL_RESOURCE_DATASIZE }, + { "stacksize", HRL_RESOURCE_STACKSIZE }, + { "coredumpsize", HRL_RESOURCE_COREDUMPSIZE }, + { "memoryuse", HRL_RESOURCE_MEMORYUSE }, + { "memorylocked", HRL_RESOURCE_MEMORYLOCKED }, + { "maxprocesses", HRL_RESOURCE_MAXPROCESSES }, + { "filedescriptors", HRL_RESOURCE_FILEDESCRIPTORS }, + { "sbsize", HRL_RESOURCE_SBSIZE }, + { "vmemoryuse", HRL_RESOURCE_VMEMORYUSE }, + { "pty", HRL_RESOURCE_PTY }, + { "swap", HRL_RESOURCE_SWAP }, + { NULL, -1 }}; + +static struct dict actionnames[] = { + { "deny", HRL_ACTION_DENY }, + { "delay", HRL_ACTION_DELAY }, + { "log", HRL_ACTION_LOG }, + { "sighup", HRL_ACTION_SIGHUP }, + { "sigint", HRL_ACTION_SIGINT }, + { "sigkill", HRL_ACTION_SIGKILL }, + { "sigsegv", HRL_ACTION_SIGSEGV }, + { "sigxcpu", HRL_ACTION_SIGXCPU }, + { "sigxfsz", HRL_ACTION_SIGXFSZ }, + { NULL, -1 }}; + +static const char * hrl_resource_name(int resource); +static void hrl_init(void); +SYSINIT(hrl, SI_SUB_CPU, SI_ORDER_FIRST, hrl_init, NULL); + +static uma_zone_t hrl_limit_zone; +static uma_zone_t hrl_rule_zone; +static struct mtx hrl_lock; + +static void hrl_compute_available(struct proc *p, int64_t (*availablep)[]); +static int hrl_rule_fully_specified(const struct hrl_rule *rule); +static void hrl_rule_to_sbuf(struct sbuf *sb, const struct hrl_rule *rule); + +MALLOC_DEFINE(M_HRL, "hrl", "Hierarchical Resource Limits"); + +static int +hrl_resource_inheritable(int resource) +{ + + switch (resource) { + case HRL_RESOURCE_MAXPROCESSES: + return (0); + default: + return (1); + } +} + +static const char * +hrl_subject_type_name(int subject) +{ + int i; + + for (i = 0; subjectnames[i].d_name != NULL; i++) { + if (subjectnames[i].d_value == subject) + return (subjectnames[i].d_name); + } + + panic("hrl_subject_type_name: unknown subject type %d", subject); +} + +static const char * +hrl_action_name(int action) +{ + int i; + + for (i = 0; actionnames[i].d_name != NULL; i++) { + if (actionnames[i].d_value == action) + return (actionnames[i].d_name); + } + + panic("hrl_action_name: unknown action %d", action); +} + +static const char * +hrl_resource_name(int resource) +{ + int i; + + for (i = 0; resourcenames[i].d_name != NULL; i++) { + if (resourcenames[i].d_value == resource) + return (resourcenames[i].d_name); + } + + panic("hrl_resource_name: unknown resource %d", resource); +} + +static void +hrl_deferred_psignal(struct proc *p, int signum) +{ + int need_lock; + + /* + * XXX: This is ugly. Either turn it into a real taskqueue, + * or think about the locking and don't lock proc here. + */ + need_lock = !PROC_LOCKED(p); + + if (need_lock) + PROC_LOCK(p); + psignal(p, signum); + if (need_lock) + PROC_UNLOCK(p); +} + +/* + * Return the amount of resource that can be allocated by 'p' before + * hitting 'rule'. + */ +static int64_t +hrl_available_resource(const struct proc *p, const struct hrl_rule *rule) +{ + int resource; + int64_t available = INT64_MAX; + struct ucred *cred = p->p_ucred; + + mtx_assert(&hrl_lock, MA_OWNED); + + resource = rule->hr_resource; + switch (rule->hr_per) { + case HRL_SUBJECT_TYPE_PROCESS: + available = rule->hr_amount - + p->p_container.hc_resources[resource]; + break; + case HRL_SUBJECT_TYPE_USER: + available = rule->hr_amount - + cred->cr_ruidinfo->ui_container.hc_resources[resource]; + break; + case HRL_SUBJECT_TYPE_LOGINCLASS: + available = rule->hr_amount - + cred->cr_loginclass->lc_container.hc_resources[resource]; + available = INT64_MAX; /* XXX */ + break; + case HRL_SUBJECT_TYPE_JAIL: + available = rule->hr_amount - + cred->cr_prison->pr_container.hc_resources[resource]; + break; + default: + panic("hrl_compute_available: unknown per %d", + rule->hr_per); + } + + return (available); +} + +/* + * Return non-zero if allocating 'amount' by proc 'p' would exceed + * resource limit specified by 'rule'. + */ +static int +hrl_would_exceed(const struct proc *p, const struct hrl_rule *rule, + int64_t amount) +{ + int64_t available; + + mtx_assert(&hrl_lock, MA_OWNED); + + available = hrl_available_resource(p, rule); + if (available >= amount) + return (0); + + /* + * We've already exceeded that one. + */ + if (available < 0) { +#ifdef notyet + KASSERT(rule->hr_action != HRL_ACTION_DENY, + ("hrl_would_exceed: deny rule already exceeded")); +#endif + return (0); + } + + return (1); +} + +/* + * Check whether the proc 'p' can allocate 'amount' of 'resource' in addition + * to what it keeps allocated now. Returns non-zero if the allocation should + * be denied, 0 otherwise. + */ +static int +hrl_enforce_proc(struct proc *p, int resource, uint64_t amount) +{ + int64_t available[HRL_RESOURCE_MAX]; + struct hrl_rule *rule; + struct hrl_limit *limit; + struct sbuf sb; + int should_deny = 0; + char *buf; + + mtx_assert(&hrl_lock, MA_OWNED); + + /* + * XXX: Do this just before we start running on a CPU, not all the time. + */ + hrl_compute_available(p, &available); + + if (available[resource] >= amount) + return (0); + + /* + * It seems we've hit a limit. Figure out what to do. There may + * be more than one matching limit; go through all of them. Denial + * should be done last, after logging and sending signals. + * + * Note that it is possible to get here, and still not trigger + * any limit, because some of the resource got freed on another + * CPU after computing contents of the 'available' array. + */ + /* + * XXX: We should sort the rules somewhat, so that 'log' and 'sig' + * rules come before before 'deny', to spare iterations over + * the p_container.hc_limits. + */ + LIST_FOREACH(limit, &p->p_container.hc_limits, hl_next) { + rule = limit->hl_rule; + if (rule->hr_resource != resource) + continue; + if (!hrl_would_exceed(p, rule, amount)) + continue; + + switch (rule->hr_action) { + case HRL_ACTION_DENY: + should_deny = 1; + break; + case HRL_ACTION_LOG: + buf = malloc(HRL_LOG_BUFSIZE, M_HRL, M_NOWAIT); + if (buf == NULL) { + printf("hrl_enforce_proc: out of memory\n"); + continue; + } + sbuf_new(&sb, buf, HRL_LOG_BUFSIZE, SBUF_FIXEDLEN); + hrl_rule_to_sbuf(&sb, rule); + sbuf_finish(&sb); + printf("resource limit \"%s\" exceeded by process %d " + "(%s), uid %d\n", sbuf_data(&sb), p->p_pid, + p->p_comm, p->p_ucred->cr_uid); + sbuf_delete(&sb); + free(buf, M_HRL); + break; + case HRL_ACTION_SIGHUP: + hrl_deferred_psignal(p, SIGHUP); + break; + case HRL_ACTION_SIGINT: + hrl_deferred_psignal(p, SIGINT); + break; + case HRL_ACTION_SIGKILL: + hrl_deferred_psignal(p, SIGKILL); + break; + case HRL_ACTION_SIGSEGV: + hrl_deferred_psignal(p, SIGSEGV); + break; + case HRL_ACTION_SIGXCPU: + hrl_deferred_psignal(p, SIGXCPU); + break; + case HRL_ACTION_SIGXFSZ: + hrl_deferred_psignal(p, SIGXFSZ); + break; + default: + panic("hrl_enforce_proc: unknown action %d", + rule->hr_action); + } + } + + if (should_deny) { + /* + * Return fake error code; the caller should change it + * into one proper for the situation - EFSIZ, ENOMEM etc. + */ + return (EDOOFUS); + } + + return (0); +} + +/* + * Go through all the rules applicable to the process, filling the array + * with amount of resource left before hitting the next limit. + */ +static void +hrl_compute_available(struct proc *p, int64_t (*availablep)[]) +{ + int i, resource; + int64_t available; + struct hrl_limit *limit; + struct hrl_rule *rule; + + mtx_assert(&hrl_lock, MA_OWNED); + + for (i = 0; i <= HRL_RESOURCE_MAX; i++) + (*availablep)[i] = INT64_MAX; + + LIST_FOREACH(limit, &p->p_container.hc_limits, hl_next) { + rule = limit->hl_rule; + resource = rule->hr_resource; + available = hrl_available_resource(p, rule); + if (available < 0) { +#ifdef notyet + KASSERT(rule->hr_action != HRL_ACTION_DENY, + ("hrl_compute_available: deny rule already exceeded")); +#endif + continue; + } + if (available < (*availablep)[resource]) + (*availablep)[resource] = available; + } +} + +static void +hrl_container_add(struct hrl_container *dest, const struct hrl_container *src) +{ + int i; + + mtx_assert(&hrl_lock, MA_OWNED); + + for (i = 0; i <= HRL_RESOURCE_MAX; i++) { + KASSERT(dest->hc_resources[i] >= 0, + ("resource usage propagation meltdown: dest < 0")); + KASSERT(src->hc_resources[i] >= 0, + ("resource usage propagation meltdown: src < 0")); + dest->hc_resources[i] += src->hc_resources[i]; + KASSERT(dest->hc_resources[i] >= 0, + ("resource usage propagation meltdown: dest < 0 after addition")); + } +} + +static void +hrl_container_subtract(struct hrl_container *dest, const struct hrl_container *src) +{ + int i; + + mtx_assert(&hrl_lock, MA_OWNED); + + for (i = 0; i <= HRL_RESOURCE_MAX; i++) { + KASSERT(dest->hc_resources[i] >= 0, + ("resource usage propagation meltdown: dest < 0")); + KASSERT(src->hc_resources[i] >= 0, + ("resource usage propagation meltdown: src < 0")); + KASSERT(src->hc_resources[i] <= dest->hc_resources[i], + ("resource usage propagation meltdown: src > dest")); + dest->hc_resources[i] -= src->hc_resources[i]; + KASSERT(dest->hc_resources[i] >= 0, + ("resource usage propagation meltdown: dest < 0 after subtraction")); + } +} + +static void +hrl_container_join(struct hrl_container *child, struct hrl_container *parent) +{ + int i; + + mtx_assert(&hrl_lock, MA_OWNED); + KASSERT(child != NULL, ("child != NULL")); + KASSERT(parent != NULL, ("parent != NULL")); + + for (i = 0; i <= HRL_HC_PARENTS_MAX; i++) { + KASSERT(child->hc_parents[i] != parent, + ("container already joined")); + if (child->hc_parents[i] == NULL) { + child->hc_parents[i] = parent; + hrl_container_add(parent, child); + return; + } + } + panic("container has too many parents"); +} + +static void +hrl_container_leave(struct hrl_container *child, struct hrl_container *parent) +{ + int i; + + mtx_assert(&hrl_lock, MA_OWNED); + KASSERT(child != NULL, ("child != NULL")); + KASSERT(parent != NULL, ("parent != NULL")); + + for (i = 0; i <= HRL_HC_PARENTS_MAX; i++) { + if (child->hc_parents[i] == parent) { + hrl_container_subtract(parent, child); + child->hc_parents[i] = NULL; + return; + } + } + panic("container not joined"); +} + +static void +hrl_container_leave_parents(struct hrl_container *child) +{ + int i; + + mtx_assert(&hrl_lock, MA_OWNED); + KASSERT(child != NULL, ("child != NULL")); + + for (i = 0; i <= HRL_HC_PARENTS_MAX; i++) { + if (child->hc_parents[i] == NULL) + continue; + hrl_container_subtract(child->hc_parents[i], child); + child->hc_parents[i] = NULL; + } +} + +void +hrl_container_create(struct hrl_container *container) +{ + int i; + + for (i = 0; i <= HRL_RESOURCE_MAX; i++) + KASSERT(container->hc_resources[i] == 0, + ("container->hc_resources[%d] != NULL", i)); + for (i = 0; i <= HRL_HC_PARENTS_MAX; i++) + KASSERT(container->hc_parents[i] == NULL, + ("container->hc_parents[%d] != NULL", i)); +} + +void +hrl_container_destroy(struct hrl_container *container) +{ + int i; + + mtx_lock(&hrl_lock); + for (i = 0; i <= HRL_RESOURCE_MAX; i++) { + if (container->hc_resources[i] != 0) + printf("destroying non-empty container: " + "%ju allocated for resource %s", + container->hc_resources[i], + hrl_resource_name(i)); + container->hc_resources[i] = 0; + } + + hrl_container_leave_parents(container); + mtx_unlock(&hrl_lock); +} + +#ifdef DIAGNOSTIC +/* + * Go through the resource consumption information and make sure it makes sense. + */ +static void +hrl_container_assert(const struct hrl_container *container) +{ + int i, resource; + struct hrl_container *parent; + + mtx_assert(&hrl_lock, MA_OWNED); + KASSERT(container != NULL, ("NULL container")); + + for (resource = 0; resource <= HRL_RESOURCE_MAX; resource++) { + KASSERT(container->hc_resources[resource] >= 0, + ("resource usage propagation meltdown: resource < 0")); + } + + for (i = 0; i <= HRL_HC_PARENTS_MAX; i++) { + parent = container->hc_parents[i]; + if (parent == NULL); + continue; + hrl_container_assert(parent); + for (resource = 0; resource <= HRL_RESOURCE_MAX; resource++) { + KASSERT(parent->hc_resources[resource] >= + container->hc_resources[resource], + ("resource usage propagation meltdown: child > parent")); + } + } +} +#endif /* DIAGNOSTIC */ + +/* + * Increase consumption of 'resource' by 'amount' for 'container' + * and all its parents. Differently from other cases, 'amount' here + * may be less than zero. + */ +static void +hrl_container_alloc_resource(struct hrl_container *container, int resource, + uint64_t amount) +{ + int i; + + mtx_assert(&hrl_lock, MA_OWNED); + KASSERT(container != NULL, ("NULL container")); + + container->hc_resources[resource] += amount; + for (i = 0; i <= HRL_HC_PARENTS_MAX; i++) { + if (container->hc_parents[i] == NULL) + continue; + hrl_container_alloc_resource(container->hc_parents[i], resource, amount); + } +#ifdef DIAGNOSTIC + hrl_container_assert(container); +#endif +} + +/* + * Increase allocation of 'resource' by 'amount' for process 'p'. + * Return 0 if it's below limits, or errno, if it's not. + */ +int +hrl_alloc(struct proc *p, int resource, uint64_t amount) +{ + int error; + +#if 0 + printf("hrl_alloc: allocating %ju of %s for %s (pid %d)\n", amount, hrl_resource_name(resource), p->p_comm, p->p_pid); +#endif + + KASSERT(amount > 0, ("hrl_alloc: invalid amount for %s: %ju", + hrl_resource_name(resource), amount)); + + mtx_lock(&hrl_lock); + error = hrl_enforce_proc(p, resource, amount); + if (error) { + mtx_unlock(&hrl_lock); + return (error); + } + hrl_container_alloc_resource(&p->p_container, resource, amount); + mtx_unlock(&hrl_lock); + + return (0); +} + +/* + * Set allocation of 'resource' to 'amount' for process 'p'. + * Return 0 if it's below limits, or errno, if it's not. + * + * Note that decreasing the allocation always returns 0, + * even if it's above the limit. + */ +int +hrl_allocated(struct proc *p, int resource, uint64_t amount) +{ + int error; + int64_t diff; + +#if 0 + printf("hrl_allocated: allocated %lld of %s for %s (pid %d)\n", amount, hrl_resource_name(resource), p->p_comm, p->p_pid); +#endif + + KASSERT(amount >= 0, ("hrl_allocated: invalid amount for %s: %ju", + hrl_resource_name(resource), amount)); + + mtx_lock(&hrl_lock); + diff = amount - p->p_container.hc_resources[resource]; + if (diff > 0) { + error = hrl_enforce_proc(p, resource, diff); + if (error) { + mtx_unlock(&hrl_lock); + return (error); + } + } + hrl_container_alloc_resource(&p->p_container, resource, diff); + mtx_unlock(&hrl_lock); + + return (0); +} + +/* + * Decrease allocation of 'resource' by 'amount' for process 'p'. + */ +void +hrl_free(struct proc *p, int resource, uint64_t amount) +{ + +#if 0 + printf("hrl_free: freeing %lld of %s for %s (pid %d)\n", amount, hrl_resource_name(resource), p->p_comm, p->p_pid); +#endif + + KASSERT(amount > 0, ("hrl_free: invalid amount for %s: %ju", + hrl_resource_name(resource), amount)); + + mtx_lock(&hrl_lock); + KASSERT(amount <= p->p_container.hc_resources[resource], + ("hrl_free: freeing %ju of %s, which is more than allocated " + "%ld for %s (pid %d)", amount, hrl_resource_name(resource), + p->p_container.hc_resources[resource], p->p_comm, p->p_pid)); + + hrl_container_alloc_resource(&p->p_container, resource, -amount); + mtx_unlock(&hrl_lock); +} + +static int +hrl_rule_matches(const struct hrl_rule *rule, const struct hrl_rule *filter) +{ + + if (filter->hr_subject_type != HRL_SUBJECT_TYPE_UNDEFINED) { + if (rule->hr_subject_type != filter->hr_subject_type) + return (0); + + switch (filter->hr_subject_type) { + case HRL_SUBJECT_TYPE_PROCESS: + if (filter->hr_subject.hs_proc != + filter->hr_subject.hs_proc) + return (0); + break; + case HRL_SUBJECT_TYPE_USER: + if (filter->hr_subject.hs_uip != + filter->hr_subject.hs_uip) + return (0); + break; + case HRL_SUBJECT_TYPE_GROUP: + if (filter->hr_subject.hs_gip != + filter->hr_subject.hs_gip) + return (0); + break; + case HRL_SUBJECT_TYPE_LOGINCLASS: + if (filter->hr_subject.hs_loginclass != + filter->hr_subject.hs_loginclass) + return (0); + break; + case HRL_SUBJECT_TYPE_JAIL: + if (filter->hr_subject.hs_prison != + filter->hr_subject.hs_prison) + return (0); + break; + default: + panic("hrl_rule_matches: unknown subject type %d", + filter->hr_subject_type); + } + } + + if (filter->hr_resource != HRL_RESOURCE_UNDEFINED) { + if (rule->hr_resource != filter->hr_resource) + return (0); + } + + if (filter->hr_action != HRL_ACTION_UNDEFINED) { + if (rule->hr_action != filter->hr_action) + return (0); + } + + if (filter->hr_amount != HRL_AMOUNT_UNDEFINED) { + if (rule->hr_amount != filter->hr_amount) + return (0); + } + + if (filter->hr_per != HRL_SUBJECT_TYPE_UNDEFINED) { + if (rule->hr_per != filter->hr_per) + return (0); + } + + return (1); +} + +static int +str2value(const char *str, int *value, struct dict *table) +{ + int i; + + if (value == NULL) + return (EINVAL); + + for (i = 0; table[i].d_name != NULL; i++) { + if (strcasecmp(table[i].d_name, str) == 0) { + *value = table[i].d_value; + return (0); + } + } + + return (EINVAL); +} + +static int +str2id(const char *str, long *value) +{ + char *end; + + if (str == NULL) + return (EINVAL); + + *value = strtoul(str, &end, 10); + if ((size_t)(end - str) != strlen(str)) + return (EINVAL); + + return (0); +} + +static int +str2int64(const char *str, int64_t *value) +{ + char *end; + + if (str == NULL) + return (EINVAL); + + *value = strtoul(str, &end, 10); + if ((size_t)(end - str) != strlen(str)) + return (EINVAL); + + return (0); +} + +/* + * Connect the rule to the container, increasing refcount for the rule. + */ +static void +hrl_container_add_rule(struct hrl_container *container, struct hrl_rule *rule) +{ + struct hrl_limit *limit; + + KASSERT(hrl_rule_fully_specified(rule), ("rule not fully specified")); + + hrl_rule_acquire(rule); + limit = uma_zalloc(hrl_limit_zone, M_WAITOK); + limit->hl_rule = rule; + + mtx_lock(&hrl_lock); + LIST_INSERT_HEAD(&container->hc_limits, limit, hl_next); + mtx_unlock(&hrl_lock); +} + +static int +hrl_container_add_rule_locked(struct hrl_container *container, struct hrl_rule *rule) +{ + struct hrl_limit *limit; + + KASSERT(hrl_rule_fully_specified(rule), ("rule not fully specified")); + mtx_assert(&hrl_lock, MA_OWNED); + + limit = uma_zalloc(hrl_limit_zone, M_NOWAIT); + if (limit == NULL) + return (ENOMEM); + hrl_rule_acquire(rule); + limit->hl_rule = rule; + + LIST_INSERT_HEAD(&container->hc_limits, limit, hl_next); + return (0); +} + +/* + * Remove limits for a rules matching the filter and release + * the refcounts for the rules, possibly freeing them. Returns + * the number of limit structures removed. + */ +static int +hrl_container_remove_rules(struct hrl_container *container, + const struct hrl_rule *filter) +{ + int removed = 0; + struct hrl_limit *limit, *limittmp; + + mtx_lock(&hrl_lock); + LIST_FOREACH_SAFE(limit, &container->hc_limits, hl_next, limittmp) { + if (!hrl_rule_matches(limit->hl_rule, filter)) + continue; + + LIST_REMOVE(limit, hl_next); + hrl_rule_release(limit->hl_rule); + uma_zfree(hrl_limit_zone, limit); + removed++; + } + mtx_unlock(&hrl_lock); + return (removed); +} + +static void +hrl_rule_acquire_subject(struct hrl_rule *rule) +{ + + switch (rule->hr_subject_type) { + case HRL_SUBJECT_TYPE_UNDEFINED: + case HRL_SUBJECT_TYPE_PROCESS: + break; + case HRL_SUBJECT_TYPE_USER: + if (rule->hr_subject.hs_uip != NULL) + uihold(rule->hr_subject.hs_uip); + break; + case HRL_SUBJECT_TYPE_GROUP: + if (rule->hr_subject.hs_gip != NULL) + gihold(rule->hr_subject.hs_gip); + break; + case HRL_SUBJECT_TYPE_LOGINCLASS: + if (rule->hr_subject.hs_loginclass != NULL) + loginclass_acquire(rule->hr_subject.hs_loginclass); + break; + case HRL_SUBJECT_TYPE_JAIL: + if (rule->hr_subject.hs_loginclass != NULL) + prison_hold(rule->hr_subject.hs_prison); + break; + default: + panic("hrl_rule_acquire_subject: unknown subject type %d", + rule->hr_subject_type); + } +} + +static void +hrl_rule_release_subject(struct hrl_rule *rule) +{ + + switch (rule->hr_subject_type) { + case HRL_SUBJECT_TYPE_UNDEFINED: + case HRL_SUBJECT_TYPE_PROCESS: + break; + case HRL_SUBJECT_TYPE_USER: + if (rule->hr_subject.hs_uip != NULL) + uifree(rule->hr_subject.hs_uip); + break; + case HRL_SUBJECT_TYPE_GROUP: + if (rule->hr_subject.hs_gip != NULL) + gifree(rule->hr_subject.hs_gip); + break; + case HRL_SUBJECT_TYPE_LOGINCLASS: + if (rule->hr_subject.hs_loginclass != NULL) + loginclass_release(rule->hr_subject.hs_loginclass); + break; + case HRL_SUBJECT_TYPE_JAIL: + if (rule->hr_subject.hs_prison != NULL) { + prison_free(rule->hr_subject.hs_prison); + sx_xunlock(&allprison_lock); + } + break; + default: + panic("hrl_rule_release_subject: unknown subject type %d", + rule->hr_subject_type); + } +} + + +struct hrl_rule * +hrl_rule_alloc(int flags) +{ + struct hrl_rule *rule; + + rule = uma_zalloc(hrl_rule_zone, flags); + if (rule == NULL) + return (NULL); + rule->hr_subject_type = HRL_SUBJECT_TYPE_UNDEFINED; + rule->hr_subject.hs_proc = NULL; + rule->hr_subject.hs_uip = NULL; + rule->hr_subject.hs_gip = NULL; + rule->hr_subject.hs_loginclass = NULL; + rule->hr_subject.hs_prison = NULL; + rule->hr_per = HRL_SUBJECT_TYPE_UNDEFINED; + rule->hr_resource = HRL_RESOURCE_UNDEFINED; + rule->hr_action = HRL_ACTION_UNDEFINED; + rule->hr_amount = HRL_AMOUNT_UNDEFINED; + refcount_init(&rule->hr_refcount, 1); + + return (rule); +} + +struct hrl_rule * +hrl_rule_duplicate(const struct hrl_rule *rule, int flags) +{ + struct hrl_rule *copy; + + copy = uma_zalloc(hrl_rule_zone, flags); + if (copy == NULL) + return (NULL); + copy->hr_subject_type = rule->hr_subject_type; + copy->hr_subject.hs_proc = rule->hr_subject.hs_proc; + copy->hr_subject.hs_uip = rule->hr_subject.hs_uip; + copy->hr_subject.hs_gip = rule->hr_subject.hs_gip; + copy->hr_subject.hs_loginclass = rule->hr_subject.hs_loginclass; + copy->hr_subject.hs_prison = rule->hr_subject.hs_prison; + copy->hr_per = rule->hr_per; + copy->hr_resource = rule->hr_resource; + copy->hr_action = rule->hr_action; + copy->hr_amount = rule->hr_amount; + refcount_init(©->hr_refcount, 1); + hrl_rule_acquire_subject(copy); + + return (copy); +} + +void +hrl_rule_acquire(struct hrl_rule *rule) +{ + + KASSERT(rule->hr_refcount > 0, ("rule->hr_refcount > 0")); + + refcount_acquire(&rule->hr_refcount); +} + +void +hrl_rule_release(struct hrl_rule *rule) +{ + + KASSERT(rule->hr_refcount > 0, ("rule->hr_refcount > 0")); + + if (refcount_release(&rule->hr_refcount)) { + hrl_rule_release_subject(rule); + uma_zfree(hrl_rule_zone, rule); + } +} + +static int +hrl_rule_fully_specified(const struct hrl_rule *rule) +{ + + switch (rule->hr_subject_type) { + case HRL_SUBJECT_TYPE_UNDEFINED: + return (0); + case HRL_SUBJECT_TYPE_PROCESS: + if (rule->hr_subject.hs_proc == NULL) + return (0); + break; + case HRL_SUBJECT_TYPE_USER: + if (rule->hr_subject.hs_uip == NULL) + return (0); + break; + case HRL_SUBJECT_TYPE_GROUP: + if (rule->hr_subject.hs_gip == NULL) + return (0); + break; + case HRL_SUBJECT_TYPE_LOGINCLASS: + if (rule->hr_subject.hs_loginclass == NULL) + return (0); + break; + case HRL_SUBJECT_TYPE_JAIL: + if (rule->hr_subject.hs_prison == NULL) + return (0); + break; + default: + panic("hrl_rule_fully_specified: unknown subject type %d", + rule->hr_subject_type); + } + if (rule->hr_resource == HRL_RESOURCE_UNDEFINED) + return (0); + if (rule->hr_action == HRL_ACTION_UNDEFINED) + return (0); + if (rule->hr_amount == HRL_AMOUNT_UNDEFINED) + return (0); + if (rule->hr_per == HRL_SUBJECT_TYPE_UNDEFINED) + return (0); + + return (1); +} + +static struct hrl_rule * +hrl_rule_from_string(char *rulestr) +{ + int error = 0; + char *subjectstr, *subject_idstr, *resourcestr, *actionstr, + *amountstr, *perstr; + struct hrl_rule *rule; + id_t id; + + rule = hrl_rule_alloc(M_WAITOK); + + subjectstr = strsep(&rulestr, ":"); + subject_idstr = strsep(&rulestr, ":"); + resourcestr = strsep(&rulestr, ":"); + actionstr = strsep(&rulestr, "=/"); + amountstr = strsep(&rulestr, "/"); + perstr = rulestr; + + if (subjectstr == NULL || subjectstr[0] == '\0') + rule->hr_subject_type = HRL_SUBJECT_TYPE_UNDEFINED; + else { + error = str2value(subjectstr, &rule->hr_subject_type, subjectnames); + if (error) + goto out; + } + + if (subject_idstr == NULL || subject_idstr[0] == '\0') { + rule->hr_subject.hs_proc = NULL; + rule->hr_subject.hs_uip = NULL; + rule->hr_subject.hs_gip = NULL; + rule->hr_subject.hs_loginclass = NULL; + rule->hr_subject.hs_prison = NULL; + } else { + + /* + * Loginclasses don't have any numerical ID's. + */ + if (rule->hr_subject_type != HRL_SUBJECT_TYPE_LOGINCLASS) { + error = str2id(subject_idstr, &id); + if (error) + goto out; + } + switch (rule->hr_subject_type) { + case HRL_SUBJECT_TYPE_UNDEFINED: + error = EINVAL; + goto out; + case HRL_SUBJECT_TYPE_PROCESS: + sx_assert(&allproc_lock, SA_LOCKED); + rule->hr_subject.hs_proc = pfind(id); + if (rule->hr_subject.hs_proc == NULL) { + error = ESRCH; + goto out; + } + PROC_UNLOCK(rule->hr_subject.hs_proc); + break; + case HRL_SUBJECT_TYPE_USER: + rule->hr_subject.hs_uip = uifind(id); + break; + case HRL_SUBJECT_TYPE_GROUP: + rule->hr_subject.hs_gip = gifind(id); + break; + case HRL_SUBJECT_TYPE_LOGINCLASS: + rule->hr_subject.hs_loginclass = loginclass_find(subject_idstr); + break; + case HRL_SUBJECT_TYPE_JAIL: + sx_xlock(&allprison_lock); + rule->hr_subject.hs_prison = prison_find(id); + if (rule->hr_subject.hs_prison == NULL) { + sx_xunlock(&allprison_lock); + error = ESRCH; + goto out; + } + break; + default: + panic("hrl_rule_from_string: unknown subject type %d", + rule->hr_subject_type); + } + } + + if (resourcestr == NULL || resourcestr[0] == '\0') + rule->hr_resource = HRL_RESOURCE_UNDEFINED; + else { + error = str2value(resourcestr, &rule->hr_resource, + resourcenames); + if (error) + goto out; + } + + if (actionstr == NULL || actionstr[0] == '\0') + rule->hr_action = HRL_ACTION_UNDEFINED; + else { + error = str2value(actionstr, &rule->hr_action, actionnames); + if (error) + goto out; + } + + if (amountstr == NULL || amountstr[0] == '\0') + rule->hr_amount = HRL_AMOUNT_UNDEFINED; + else { + error = str2int64(amountstr, &rule->hr_amount); + if (error) + goto out; + } + + if (perstr == NULL || perstr[0] == '\0') + rule->hr_per = HRL_SUBJECT_TYPE_UNDEFINED; + else { + error = str2value(perstr, &rule->hr_per, subjectnames); + if (error) + goto out; + } + +out: + if (error) { + hrl_rule_release(rule); + return (NULL); + } + + return (rule); +} + +/* + * Link a rule with subjects to which it applies. + */ +int +hrl_rule_add(struct hrl_rule *rule) +{ + struct proc *p; + struct ucred *cred; + struct uidinfo *uip; + struct gidinfo *gip; + struct prison *pr; + struct loginclass *lc; + + KASSERT(hrl_rule_fully_specified(rule), ("rule not fully specified")); + + if (rule->hr_subject_type == HRL_SUBJECT_TYPE_GROUP || + rule->hr_per == HRL_SUBJECT_TYPE_GROUP) + return (EOPNOTSUPP); + + if (rule->hr_action == HRL_ACTION_DELAY) + return (EOPNOTSUPP); + + /* + * Make sure there are no duplicated rules. + */ + hrl_rule_remove(rule); + + switch (rule->hr_subject_type) { + case HRL_SUBJECT_TYPE_PROCESS: + p = rule->hr_subject.hs_proc; + KASSERT(p != NULL, ("hrl_rule_add: NULL proc")); + hrl_container_add_rule(&p->p_container, rule); + /* + * In case of per-process rule, we don't have anything more + * to do. Also, there is no point in increasing reference + * count, as the per-process containers never have + * any subcontainers. + */ + return (0); + + case HRL_SUBJECT_TYPE_USER: + uip = rule->hr_subject.hs_uip; + KASSERT(uip != NULL, ("hrl_rule_add: NULL uip")); + hrl_container_add_rule(&uip->ui_container, rule); + break; + + case HRL_SUBJECT_TYPE_GROUP: + gip = rule->hr_subject.hs_gip; + KASSERT(gip != NULL, ("hrl_rule_add: NULL gip")); + hrl_container_add_rule(&gip->gi_container, rule); + break; + + case HRL_SUBJECT_TYPE_LOGINCLASS: + lc = rule->hr_subject.hs_loginclass; + KASSERT(lc != NULL, ("hrl_rule_add: NULL loginclass")); + hrl_container_add_rule(&lc->lc_container, rule); + break; + + case HRL_SUBJECT_TYPE_JAIL: + pr = rule->hr_subject.hs_prison; + KASSERT(pr != NULL, ("hrl_rule_add: NULL pr")); + hrl_container_add_rule(&pr->pr_container, rule); + break; + + default: + panic("hrl_rule_add: unknown subject type %d", + rule->hr_subject_type); + } + + /* + * Now go through all the processes and add the new rule to the ones + * it applies to. + */ + sx_assert(&allproc_lock, SA_LOCKED); + FOREACH_PROC_IN_SYSTEM(p) { + cred = p->p_ucred; + switch (rule->hr_subject_type) { + case HRL_SUBJECT_TYPE_USER: + if (cred->cr_uidinfo == rule->hr_subject.hs_uip || + cred->cr_ruidinfo == rule->hr_subject.hs_uip) + break; + continue; + case HRL_SUBJECT_TYPE_GROUP: + if (groupmember(rule->hr_subject.hs_gip->gi_gid, cred)) + break; + continue; + case HRL_SUBJECT_TYPE_LOGINCLASS: + if (cred->cr_loginclass == rule->hr_subject.hs_loginclass) + break; + continue; + case HRL_SUBJECT_TYPE_JAIL: + for (pr = cred->cr_prison; pr != NULL; pr = pr->pr_parent) + if (pr->pr_id == rule->hr_subject.hs_prison->pr_id) + break; + continue; + default: + panic("hrl_rule_add: unknown subject type %d", + rule->hr_subject_type); + } + + hrl_container_add_rule(&p->p_container, rule); + } + + return (0); +} + +static int +hrl_rule_remove_callback(struct hrl_container *container, const struct hrl_rule *filter, void *arg3) +{ + int *found = (int *)arg3; + + *found += hrl_container_remove_rules(container, filter); + return (0); +} + +/* + * Remove all rules that match the filter. + */ +int +hrl_rule_remove(const struct hrl_rule *filter) +{ + int error, found = 0; + struct proc *p; + + if (filter->hr_subject_type == HRL_SUBJECT_TYPE_PROCESS && + filter->hr_subject.hs_proc != NULL) { + p = filter->hr_subject.hs_proc; + found = hrl_container_remove_rules(&p->p_container, filter); + if (found) + return (0); + return (ESRCH); + } + + error = loginclass_container_foreach(hrl_rule_remove_callback, filter, + (void *)&found); + KASSERT(error == 0, ("loginclass_container_foreach failed")); + error = ui_container_foreach(hrl_rule_remove_callback, filter, + (void *)&found); + KASSERT(error == 0, ("ui_container_foreach failed")); + error = gi_container_foreach(hrl_rule_remove_callback, filter, + (void *)&found); + KASSERT(error == 0, ("gi_container_foreach failed")); + + sx_assert(&allproc_lock, SA_LOCKED); + FOREACH_PROC_IN_SYSTEM(p) { + found += hrl_container_remove_rules(&p->p_container, filter); + if (error == 0) + found = 1; + } + + if (found) + return (0); + return (ESRCH); +} + +/* + * Appends a rule to the sbuf. + */ +static void +hrl_rule_to_sbuf(struct sbuf *sb, const struct hrl_rule *rule) +{ + + sbuf_printf(sb, "%s:", hrl_subject_type_name(rule->hr_subject_type)); + + switch (rule->hr_subject_type) { + case HRL_SUBJECT_TYPE_PROCESS: + if (rule->hr_subject.hs_proc == NULL) + sbuf_printf(sb, ":"); + else + sbuf_printf(sb, "%d:", rule->hr_subject.hs_proc->p_pid); + break; + case HRL_SUBJECT_TYPE_USER: + if (rule->hr_subject.hs_uip == NULL) + sbuf_printf(sb, ":"); + else + sbuf_printf(sb, "%d:", rule->hr_subject.hs_uip->ui_uid); + break; + case HRL_SUBJECT_TYPE_GROUP: + if (rule->hr_subject.hs_gip == NULL) + sbuf_printf(sb, ":"); + else + sbuf_printf(sb, "%d:", rule->hr_subject.hs_gip->gi_gid); + break; + case HRL_SUBJECT_TYPE_LOGINCLASS: + if (rule->hr_subject.hs_loginclass == NULL) + sbuf_printf(sb, ":"); + else + sbuf_printf(sb, "%s:", rule->hr_subject.hs_loginclass->lc_name); + break; + case HRL_SUBJECT_TYPE_JAIL: + if (rule->hr_subject.hs_prison == NULL) + sbuf_printf(sb, ":"); + else + sbuf_printf(sb, "%d:", rule->hr_subject.hs_prison->pr_id); + break; + default: + panic("hrl_rule_to_sbuf: unknown subject type %d", + rule->hr_subject_type); + } + + sbuf_printf(sb, "%s:%s=%jd", + hrl_resource_name(rule->hr_resource), + hrl_action_name(rule->hr_action), + rule->hr_amount); + + if (rule->hr_per != rule->hr_subject_type) + sbuf_printf(sb, "/%s", hrl_subject_type_name(rule->hr_per)); +} + +/* + * Routine used by HRL syscalls to read in input string. + */ +static int +hrl_read_inbuf(char **inputstr, const char *inbufp, size_t inbuflen) +{ + int error; + char *str; + + if (inbuflen <= 0) + return (EINVAL); + + str = malloc(inbuflen + 1, M_HRL, M_WAITOK); + error = copyinstr(inbufp, str, inbuflen, NULL); + if (error) { + free(str, M_HRL); + return (error); + } + + *inputstr = str; + + return (0); +} + +/* + * Routine used by HRL syscalls to write out output string. + */ +static int +hrl_write_outbuf(struct sbuf *outputsbuf, char *outbufp, size_t outbuflen) +{ + int error; + + if (outputsbuf == NULL) + return (0); + + sbuf_finish(outputsbuf); + if (outbuflen < sbuf_len(outputsbuf) + 1) { + sbuf_delete(outputsbuf); + return (ERANGE); + } + error = copyout(sbuf_data(outputsbuf), outbufp, + sbuf_len(outputsbuf) + 1); + sbuf_delete(outputsbuf); + return (error); +} + +static struct sbuf * +hrl_container_to_sbuf(struct hrl_container *container) +{ + int i; + struct sbuf *sb; + + sb = sbuf_new_auto(); + for (i = 0; i <= HRL_RESOURCE_MAX; i++) { + sbuf_printf(sb, "%s=%jd,", hrl_resource_name(i), + container->hc_resources[i]); + } + sbuf_setpos(sb, sbuf_len(sb) - 1); + return (sb); +} + +int +hrl_get_usage(struct thread *td, struct hrl_get_usage_args *uap) +{ + int error; + char *inputstr; + struct hrl_rule *filter; + struct sbuf *outputsbuf = NULL; + struct proc *p; + struct uidinfo *uip; + struct gidinfo *gip; + struct loginclass *lc; + struct prison *pr; + + error = hrl_read_inbuf(&inputstr, uap->inbufp, uap->inbuflen); + if (error) + return (error); + + sx_slock(&allproc_lock); + filter = hrl_rule_from_string(inputstr); + free(inputstr, M_HRL); + if (filter == NULL) { + sx_sunlock(&allproc_lock); + return (EINVAL); + } + + switch (filter->hr_subject_type) { + case HRL_SUBJECT_TYPE_PROCESS: + p = filter->hr_subject.hs_proc; + if (p == NULL) { + error = EINVAL; + goto out; + } + outputsbuf = hrl_container_to_sbuf(&p->p_container); + break; + case HRL_SUBJECT_TYPE_USER: + uip = filter->hr_subject.hs_uip; + if (uip == NULL) { + error = EINVAL; + goto out; + } + outputsbuf = hrl_container_to_sbuf(&uip->ui_container); + break; + case HRL_SUBJECT_TYPE_GROUP: + gip = filter->hr_subject.hs_gip; + if (gip == NULL) { + error = EINVAL; + goto out; + } + outputsbuf = hrl_container_to_sbuf(&gip->gi_container); + break; + case HRL_SUBJECT_TYPE_LOGINCLASS: + lc = filter->hr_subject.hs_loginclass; + if (lc == NULL) { + error = EINVAL; + goto out; + } + outputsbuf = hrl_container_to_sbuf(&lc->lc_container); + break; + case HRL_SUBJECT_TYPE_JAIL: + pr = filter->hr_subject.hs_prison; + if (pr == NULL) { + error = EINVAL; + goto out; + } + outputsbuf = hrl_container_to_sbuf(&pr->pr_container); + break; + default: + error = EINVAL; + } +out: + hrl_rule_release(filter); + sx_sunlock(&allproc_lock); + if (error) + return (error); + + error = hrl_write_outbuf(outputsbuf, uap->outbufp, uap->outbuflen); + + return (error); +} + +static int +hrl_get_rules_callback(struct hrl_container *container, + const struct hrl_rule *filter, void *arg3) +{ + struct hrl_limit *limit; + struct sbuf *sb = (struct sbuf *)arg3; + + mtx_assert(&hrl_lock, MA_OWNED); + + LIST_FOREACH(limit, &container->hc_limits, hl_next) { + if (!hrl_rule_matches(limit->hl_rule, filter)) + continue; + hrl_rule_to_sbuf(sb, limit->hl_rule); + sbuf_printf(sb, ","); + } + + return (0); +} + +int +hrl_get_rules(struct thread *td, struct hrl_get_rules_args *uap) +{ + int error; + size_t bufsize = HRL_DEFAULT_BUFSIZE; + char *inputstr, *buf; + struct sbuf *sb; + struct hrl_rule *filter; + struct hrl_limit *limit; + struct proc *p; + + error = hrl_read_inbuf(&inputstr, uap->inbufp, uap->inbuflen); + if (error) + return (error); + + sx_slock(&allproc_lock); + filter = hrl_rule_from_string(inputstr); + free(inputstr, M_HRL); + if (filter == NULL) { + sx_sunlock(&allproc_lock); + return (EINVAL); + } + +again: + buf = malloc(bufsize, M_HRL, M_WAITOK); + sb = sbuf_new(NULL, buf, bufsize, SBUF_FIXEDLEN); + KASSERT(sb != NULL, ("sbuf_new failed")); + + sx_assert(&allproc_lock, SA_LOCKED); + FOREACH_PROC_IN_SYSTEM(p) { + mtx_lock(&hrl_lock); + LIST_FOREACH(limit, &p->p_container.hc_limits, hl_next) { + /* + * Non-process rules will be added to the buffer later. + * Adding them here would result in duplicated output. + */ + if (limit->hl_rule->hr_subject_type != HRL_SUBJECT_TYPE_PROCESS) + continue; + if (!hrl_rule_matches(limit->hl_rule, filter)) + continue; + hrl_rule_to_sbuf(sb, limit->hl_rule); + sbuf_printf(sb, ","); + } + mtx_unlock(&hrl_lock); + } + + mtx_lock(&hrl_lock); + loginclass_container_foreach(hrl_get_rules_callback, filter, sb); + ui_container_foreach(hrl_get_rules_callback, filter, sb); + gi_container_foreach(hrl_get_rules_callback, filter, sb); + mtx_unlock(&hrl_lock); + if (sbuf_overflowed(sb)) { + sbuf_delete(sb); + free(buf, M_HRL); + bufsize *= 4; + goto again; + } + + /* + * Remove trailing ",". + */ + if (sbuf_len(sb) > 0) + sbuf_setpos(sb, sbuf_len(sb) - 1); + + error = hrl_write_outbuf(sb, uap->outbufp, uap->outbuflen); + + hrl_rule_release(filter); + sx_sunlock(&allproc_lock); + free(buf, M_HRL); + return (error); +} + +int +hrl_get_limits(struct thread *td, struct hrl_get_limits_args *uap) +{ + int error; + size_t bufsize = HRL_DEFAULT_BUFSIZE; + char *inputstr, *buf; + struct sbuf *sb; + struct hrl_rule *filter; + struct hrl_limit *limit; + + error = hrl_read_inbuf(&inputstr, uap->inbufp, uap->inbuflen); + if (error) + return (error); + + sx_slock(&allproc_lock); + filter = hrl_rule_from_string(inputstr); + free(inputstr, M_HRL); + if (filter == NULL) { + sx_sunlock(&allproc_lock); + return (EINVAL); + } + + if (filter->hr_subject_type == HRL_SUBJECT_TYPE_UNDEFINED) { + hrl_rule_release(filter); + sx_sunlock(&allproc_lock); + return (EINVAL); + } + if (filter->hr_subject_type != HRL_SUBJECT_TYPE_PROCESS) { + hrl_rule_release(filter); + sx_sunlock(&allproc_lock); + return (EOPNOTSUPP); + } + if (filter->hr_subject.hs_proc == NULL) { + hrl_rule_release(filter); + sx_sunlock(&allproc_lock); + return (EINVAL); + } + +again: + buf = malloc(bufsize, M_HRL, M_WAITOK); + sb = sbuf_new(NULL, buf, bufsize, SBUF_FIXEDLEN); + KASSERT(sb != NULL, ("sbuf_new failed")); + + mtx_lock(&hrl_lock); + LIST_FOREACH(limit, &filter->hr_subject.hs_proc->p_container.hc_limits, hl_next) { + hrl_rule_to_sbuf(sb, limit->hl_rule); + sbuf_printf(sb, ","); + } + mtx_unlock(&hrl_lock); + if (sbuf_overflowed(sb)) { + sbuf_delete(sb); + free(buf, M_HRL); + bufsize *= 4; + goto again; + } + + /* + * Remove trailing ",". + */ + if (sbuf_len(sb) > 0) + sbuf_setpos(sb, sbuf_len(sb) - 1); + + error = hrl_write_outbuf(sb, uap->outbufp, uap->outbuflen); + hrl_rule_release(filter); + sx_sunlock(&allproc_lock); + free(buf, M_HRL); + return (error); +} + +int +hrl_add_rule(struct thread *td, struct hrl_add_rule_args *uap) +{ + int error; + struct hrl_rule *rule; + char *inputstr; + + error = priv_check(td, PRIV_HRL_SET); + if (error) + return (error); + + error = hrl_read_inbuf(&inputstr, uap->inbufp, uap->inbuflen); + if (error) + return (error); + + sx_slock(&allproc_lock); + rule = hrl_rule_from_string(inputstr); + free(inputstr, M_HRL); + if (rule == NULL) { + sx_sunlock(&allproc_lock); + return (EINVAL); + } + /* + * The 'per' part of a rule is optional. + */ + if (rule->hr_per == HRL_SUBJECT_TYPE_UNDEFINED && + rule->hr_subject_type != HRL_SUBJECT_TYPE_UNDEFINED) + rule->hr_per = rule->hr_subject_type; + + if (!hrl_rule_fully_specified(rule)) { + error = EINVAL; + goto out; + } + + error = hrl_rule_add(rule); + +out: + hrl_rule_release(rule); + sx_sunlock(&allproc_lock); + return (error); +} + +int +hrl_remove_rule(struct thread *td, struct hrl_remove_rule_args *uap) +{ + int error; + struct hrl_rule *filter; + char *inputstr; + + error = priv_check(td, PRIV_HRL_SET); + if (error) + return (error); + + error = hrl_read_inbuf(&inputstr, uap->inbufp, uap->inbuflen); + if (error) + return (error); + + sx_slock(&allproc_lock); + filter = hrl_rule_from_string(inputstr); + free(inputstr, M_HRL); + if (filter == NULL) { + sx_sunlock(&allproc_lock); + return (EINVAL); + } + + error = hrl_rule_remove(filter); + hrl_rule_release(filter); + sx_sunlock(&allproc_lock); + + return (error); +} + +/* + * Called from kern/init_main.c, for proc0 and initproc. + */ +void +hrl_proc_init(struct proc *p) +{ + struct ucred *cred = p->p_ucred; + + mtx_lock(&hrl_lock); + + hrl_container_create(&p->p_container); + hrl_container_join(&p->p_container, &cred->cr_ruidinfo->ui_container); + hrl_container_join(&p->p_container, &cred->cr_loginclass->lc_container); + hrl_container_join(&p->p_container, &cred->cr_prison->pr_container); + + mtx_unlock(&hrl_lock); +} + +/* + * Called before credentials change, to adjust HRL data structures + * assigned to the process. + */ +void +hrl_proc_ucred_changing(struct proc *p, struct ucred *newcred) +{ + int error; + struct hrl_limit *limit; + struct uidinfo *olduip, *newuip; + struct loginclass *oldlc, *newlc; + struct prison *oldpr, *newpr; + + PROC_LOCK_ASSERT(p, MA_OWNED); + + newuip = newcred->cr_ruidinfo; + olduip = p->p_ucred->cr_ruidinfo; + newlc = newcred->cr_loginclass; + oldlc = p->p_ucred->cr_loginclass; + newpr = newcred->cr_prison; + oldpr = p->p_ucred->cr_prison; + + mtx_lock(&hrl_lock); + + /* + * Remove rules that are no longer applicable with the new ucred. + */ + LIST_FOREACH(limit, &p->p_container.hc_limits, hl_next) { + switch (limit->hl_rule->hr_subject_type) { + case HRL_SUBJECT_TYPE_PROCESS: + continue; + case HRL_SUBJECT_TYPE_USER: + if (newuip == olduip) + continue; + break; + case HRL_SUBJECT_TYPE_LOGINCLASS: + if (newlc == oldlc) + continue; + break; + case HRL_SUBJECT_TYPE_JAIL: + if (newpr == oldpr) + continue; + break; + default: + panic("hrl_proc_ucred_changing: unknown subject %d", + limit->hl_rule->hr_subject_type); + } + + LIST_REMOVE(limit, hl_next); + hrl_rule_release(limit->hl_rule); + uma_zfree(hrl_limit_zone, limit); + } + + /* + * Add rules for the new ucred and move between containers where applicable. + */ + if (newuip != olduip) { + LIST_FOREACH(limit, &newuip->ui_container.hc_limits, hl_next) { + error = hrl_container_add_rule_locked(&p->p_container, limit->hl_rule); + KASSERT(error == 0, ("XXX: better error handling needed")); + } + + hrl_container_leave(&p->p_container, &olduip->ui_container); + hrl_container_join(&p->p_container, &newuip->ui_container); + } + if (newlc != oldlc) { + LIST_FOREACH(limit, &newlc->lc_container.hc_limits, hl_next) { + error = hrl_container_add_rule_locked(&p->p_container, limit->hl_rule); + KASSERT(error == 0, ("XXX: better error handling needed")); + } + + hrl_container_leave(&p->p_container, &oldlc->lc_container); + hrl_container_join(&p->p_container, &newlc->lc_container); + } + if (newpr != oldpr) { + LIST_FOREACH(limit, &newpr->pr_container.hc_limits, hl_next) { + error = hrl_container_add_rule_locked(&p->p_container, limit->hl_rule); + KASSERT(error == 0, ("XXX: better error handling needed")); + } + + hrl_container_leave(&p->p_container, &oldpr->pr_container); + hrl_container_join(&p->p_container, &newpr->pr_container); + } + + mtx_unlock(&hrl_lock); +} + +/* + * Inherit resource usage information and copy limits from the parent + * process to the child. + */ +static void +hrl_proc_fork(void *arg __unused, struct proc *parent, struct proc *child, + int flags __unused) +{ + int error, i; + struct hrl_limit *limit; + struct hrl_rule *rule; + struct hrl_container *container; + + PROC_LOCK(parent); + PROC_LOCK(child); + mtx_lock(&hrl_lock); + + /* + * Create container for the child process and inherit containing + * containers from the parent. + */ + hrl_container_create(&child->p_container); + for (i = 0; i <= HRL_HC_PARENTS_MAX; i++) { + container = parent->p_container.hc_parents[i]; + if (container == NULL) + continue; + hrl_container_join(&child->p_container, container); + } + + for (i = 0; i <= HRL_RESOURCE_MAX; i++) { + if (parent->p_container.hc_resources[i] != 0 && + hrl_resource_inheritable(i)) + hrl_allocated(child, i, + parent->p_container.hc_resources[i]); + } + + /* + * Go through limits applicable to the parent and assign them to the child. + * Rules with 'process' subject have to be duplicated in order to make their + * hr_subject point to the new process. + */ + LIST_FOREACH(limit, &parent->p_container.hc_limits, hl_next) { + if (limit->hl_rule->hr_subject_type == HRL_SUBJECT_TYPE_PROCESS) { + rule = hrl_rule_duplicate(limit->hl_rule, M_NOWAIT); + KASSERT(rule != NULL, ("XXX: better error handling needed")); + KASSERT(rule->hr_subject.hs_proc == parent, + ("rule->hr_subject.hs_proc == parent")); + rule->hr_subject.hs_proc = child; + error = hrl_container_add_rule_locked(&child->p_container, rule); + KASSERT(error == 0, ("XXX: better error handling needed")); + hrl_rule_release(rule); + } else { + error = hrl_container_add_rule_locked(&child->p_container, limit->hl_rule); + KASSERT(error == 0, ("XXX: better error handling needed")); + } + } + + mtx_unlock(&hrl_lock); + PROC_UNLOCK(child); + PROC_UNLOCK(parent); +} + +void +hrl_proc_exiting(struct proc *p) +{ + /* + * XXX: Free these three some other way. + */ + hrl_allocated(p, HRL_RESOURCE_FILESIZE, 0); + hrl_allocated(p, HRL_RESOURCE_COREDUMPSIZE, 0); + hrl_allocated(p, HRL_RESOURCE_PTY, 0); + + mtx_lock(&hrl_lock); + hrl_container_destroy(&p->p_container); + mtx_unlock(&hrl_lock); +} + +/* + * Go through the process' limits, freeing them. + */ +static void +hrl_proc_exit(void *arg __unused, struct proc *p) +{ + struct hrl_limit *limit; + + mtx_lock(&hrl_lock); + while (!LIST_EMPTY(&p->p_container.hc_limits)) { + limit = LIST_FIRST(&p->p_container.hc_limits); + LIST_REMOVE(limit, hl_next); + hrl_rule_release(limit->hl_rule); + uma_zfree(hrl_limit_zone, limit); + } + mtx_unlock(&hrl_lock); +} + +static void +hrl_init(void) +{ + + hrl_limit_zone = uma_zcreate("hrl_limit", sizeof(struct hrl_limit), + NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE); + hrl_rule_zone = uma_zcreate("hrl_rule", sizeof(struct hrl_rule), + NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE); + mtx_init(&hrl_lock, "hrl lock", NULL, MTX_RECURSE); /* XXX: Make it non-recurseable later. */ + EVENTHANDLER_REGISTER(process_fork, hrl_proc_fork, NULL, + EVENTHANDLER_PRI_ANY); + EVENTHANDLER_REGISTER(process_exit, hrl_proc_exit, NULL, + EVENTHANDLER_PRI_ANY); +} diff -urNp current/sys/kern/kern_jail.c hrl/sys/kern/kern_jail.c --- current/sys/kern/kern_jail.c 2009-12-18 11:18:00.709667463 +0100 +++ hrl/sys/kern/kern_jail.c 2009-12-18 11:28:31.199643424 +0100 @@ -1156,6 +1156,7 @@ kern_jail_set(struct thread *td, struct root = mypr->pr_root; vref(root); } + hrl_container_create(&pr->pr_container); strlcpy(pr->pr_hostuuid, DEFAULT_HOSTUUID, HOSTUUIDLEN); pr->pr_flags |= PR_HOST; #if defined(INET) || defined(INET6) @@ -2251,7 +2252,7 @@ do_jail_attach(struct thread *td, struct setsugid(p); crcopy(newcred, oldcred); newcred->cr_prison = pr; - p->p_ucred = newcred; + change_cred(p, newcred); PROC_UNLOCK(p); crfree(oldcred); prison_deref(ppr, PD_DEREF | PD_DEUREF); @@ -2485,6 +2486,7 @@ prison_deref(struct prison *pr, int flag if (pr->pr_cpuset != NULL) cpuset_rel(pr->pr_cpuset); osd_jail_exit(pr); + hrl_container_destroy(&pr->pr_container); free(pr, M_PRISON); /* Removing a prison frees a reference on its parent. */ diff -urNp current/sys/kern/kern_loginclass.c hrl/sys/kern/kern_loginclass.c --- current/sys/kern/kern_loginclass.c 1970-01-01 01:00:00.000000000 +0100 +++ hrl/sys/kern/kern_loginclass.c 2009-12-18 11:28:31.634046958 +0100 @@ -0,0 +1,232 @@ +/*- + * Copyright (c) 2009 Edward Tomasz Napierała + * 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. + */ + +/* + * Processes may set login class name using setloginclass(2). This + * is usually done through call to setusercontext(3), by programs + * such as login(1), based on information from master.passwd(5). Kernel + * uses this information to enforce per-class resource limits. Current + * login class can be determined using id(1). Login class is inherited + * from the parent process during fork(2). If not set, it defaults + * to "default". + * + * Code in this file implements setloginclass(2) and getloginclass(2) + * system calls, and maintains class name storage and retrieval. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * XXX: Review locking. + */ + +/* XXX: Use UMA instead? */ +static MALLOC_DEFINE(M_LOGINCLASS, "loginclass", "loginclass structures"); + +LIST_HEAD(, loginclass) loginclasses; + +/* + * Lock protecting loginclasses list. + */ +static struct mtx loginclasses_lock; + +static void lc_init(void); +SYSINIT(hrl, SI_SUB_CPU, SI_ORDER_FIRST, lc_init, NULL); + +void +loginclass_acquire(struct loginclass *lc) +{ + + refcount_acquire(&lc->lc_refcount); +} + +void +loginclass_release(struct loginclass *lc) +{ + + mtx_lock(&loginclasses_lock); + if (refcount_release(&lc->lc_refcount)) { + hrl_container_destroy(&lc->lc_container); + LIST_REMOVE(lc, lc_next); + mtx_unlock(&loginclasses_lock); + free(lc, M_LOGINCLASS); + + return; + } + mtx_unlock(&loginclasses_lock); +} + +/* + * Return loginclass structure with a corresponding name. Not + * performance critical, as it's used mainly by setloginclass(2), + * which happens once per login session. Caller has to use + * loginclass_release() on the returned value when it's no longer + * needed. + */ +struct loginclass * +loginclass_find(const char *name) +{ + struct loginclass *lc, *newlc; + + KASSERT(strlen(name) <= MAXLOGNAME - 1, + ("loginclass_find: got too long name")); + + newlc = malloc(sizeof(*newlc), M_LOGINCLASS, M_ZERO | M_WAITOK); + + mtx_lock(&loginclasses_lock); + LIST_FOREACH(lc, &loginclasses, lc_next) { + if (strcmp(name, lc->lc_name) != 0) + continue; + + /* Found loginclass with a matching name? */ + loginclass_acquire(lc); + mtx_unlock(&loginclasses_lock); + free(newlc, M_LOGINCLASS); + return (lc); + } + + /* Add new loginclass. */ + hrl_container_create(&newlc->lc_container); + strcpy(newlc->lc_name, name); + refcount_init(&newlc->lc_refcount, 1); + LIST_INSERT_HEAD(&loginclasses, newlc, lc_next); + mtx_unlock(&loginclasses_lock); + + return (newlc); +} + +/* + * Get login class name. + */ +#ifndef _SYS_SYSPROTO_H_ +struct getloginclass_args { + char *namebuf; + size_t namelen; +}; +#endif +/* ARGSUSED */ +int +getloginclass(struct thread *td, struct getloginclass_args *uap) +{ + int error = 0; + size_t lcnamelen; + struct proc *p = td->td_proc; + struct loginclass *lc; + + PROC_LOCK(p); + lc = p->p_ucred->cr_loginclass; + loginclass_acquire(lc); + PROC_UNLOCK(p); + + lcnamelen = strlen(lc->lc_name) + 1; + if (lcnamelen > uap->namelen) + error = ERANGE; + if (error == 0) + error = copyout(lc->lc_name, uap->namebuf, lcnamelen); + loginclass_release(lc); + return (error); +} + +/* + * Set login class name. + */ +#ifndef _SYS_SYSPROTO_H_ +struct setloginclass_args { + const char *namebuf; +}; +#endif +/* ARGSUSED */ +int +setloginclass(struct thread *td, struct setloginclass_args *uap) +{ + struct proc *p = td->td_proc; + int error; + char lcname[MAXLOGNAME]; + struct loginclass *newlc; + struct ucred *newcred, *oldcred; + + error = priv_check(td, PRIV_PROC_SETLOGINCLASS); + if (error) + return (error); + error = copyinstr(uap->namebuf, lcname, sizeof(lcname), NULL); + if (error == ENAMETOOLONG) + return (EINVAL); + + newcred = crget(); + newlc = loginclass_find(lcname); + + PROC_LOCK(p); + oldcred = crcopysafe(p, newcred); + newcred->cr_loginclass = newlc; + change_cred(p, newcred); + PROC_UNLOCK(p); + + loginclass_release(oldcred->cr_loginclass); + crfree(oldcred); + + return (0); +} + +int +loginclass_container_foreach(int (*callback)(struct hrl_container *container, + const struct hrl_rule *filter, void *arg3), + const struct hrl_rule *filter, void *arg3) +{ + int error; + struct loginclass *lc, *lctmp; + + LIST_FOREACH_SAFE(lc, &loginclasses, lc_next, lctmp) { + error = (callback)(&lc->lc_container, filter, arg3); + if (error) + return (error); + } + + return (0); +} + +static void +lc_init(void) +{ + + mtx_init(&loginclasses_lock, "loginclasses lock", NULL, MTX_DEF); +} diff -urNp current/sys/kern/kern_proc.c hrl/sys/kern/kern_proc.c --- current/sys/kern/kern_proc.c 2009-12-18 11:18:01.113676632 +0100 +++ hrl/sys/kern/kern_proc.c 2009-12-18 11:28:31.977438087 +0100 @@ -165,6 +165,7 @@ procinit() proc_ctor, proc_dtor, proc_init, proc_fini, UMA_ALIGN_PTR, UMA_ZONE_NOFREE); uihashinit(); + gihashinit(); } /* diff -urNp current/sys/kern/kern_prot.c hrl/sys/kern/kern_prot.c --- current/sys/kern/kern_prot.c 2009-12-18 11:18:01.154034275 +0100 +++ hrl/sys/kern/kern_prot.c 2009-12-18 11:28:32.148985868 +0100 @@ -51,9 +51,11 @@ __FBSDID("$FreeBSD: src/sys/kern/kern_pr #include #include #include +#include #include #include #include +#include #include #include #include @@ -570,7 +572,7 @@ setuid(struct thread *td, struct setuid_ change_euid(newcred, uip); setsugid(p); } - p->p_ucred = newcred; + change_cred(p, newcred); PROC_UNLOCK(p); uifree(uip); crfree(oldcred); @@ -626,7 +628,7 @@ seteuid(struct thread *td, struct seteui change_euid(newcred, euip); setsugid(p); } - p->p_ucred = newcred; + change_cred(p, newcred); PROC_UNLOCK(p); uifree(euip); crfree(oldcred); @@ -726,7 +728,7 @@ setgid(struct thread *td, struct setgid_ change_egid(newcred, gid); setsugid(p); } - p->p_ucred = newcred; + change_cred(p, newcred); PROC_UNLOCK(p); crfree(oldcred); return (0); @@ -772,7 +774,7 @@ setegid(struct thread *td, struct setegi change_egid(newcred, egid); setsugid(p); } - p->p_ucred = newcred; + change_cred(p, newcred); PROC_UNLOCK(p); crfree(oldcred); return (0); @@ -845,7 +847,7 @@ kern_setgroups(struct thread *td, u_int crsetgroups_locked(newcred, ngrp, groups); } setsugid(p); - p->p_ucred = newcred; + change_cred(p, newcred); PROC_UNLOCK(p); crfree(oldcred); return (0); @@ -908,7 +910,7 @@ setreuid(register struct thread *td, str change_svuid(newcred, newcred->cr_uid); setsugid(p); } - p->p_ucred = newcred; + change_cred(p, newcred); PROC_UNLOCK(p); uifree(ruip); uifree(euip); @@ -972,7 +974,7 @@ setregid(register struct thread *td, str change_svgid(newcred, newcred->cr_groups[0]); setsugid(p); } - p->p_ucred = newcred; + change_cred(p, newcred); PROC_UNLOCK(p); crfree(oldcred); return (0); @@ -1046,7 +1048,7 @@ setresuid(register struct thread *td, st change_svuid(newcred, suid); setsugid(p); } - p->p_ucred = newcred; + change_cred(p, newcred); PROC_UNLOCK(p); uifree(ruip); uifree(euip); @@ -1122,7 +1124,7 @@ setresgid(register struct thread *td, st change_svgid(newcred, sgid); setsugid(p); } - p->p_ucred = newcred; + change_cred(p, newcred); PROC_UNLOCK(p); crfree(oldcred); return (0); @@ -1831,6 +1833,7 @@ crfree(struct ucred *cr) */ if (cr->cr_prison != NULL) prison_free(cr->cr_prison); + loginclass_release(cr->cr_loginclass); #ifdef AUDIT audit_cred_destroy(cr); #endif @@ -1867,6 +1870,7 @@ crcopy(struct ucred *dest, struct ucred uihold(dest->cr_uidinfo); uihold(dest->cr_ruidinfo); prison_hold(dest->cr_prison); + loginclass_acquire(dest->cr_loginclass); #ifdef AUDIT audit_cred_copy(src, dest); #endif @@ -2106,6 +2110,19 @@ setsugid(struct proc *p) p->p_stops = 0; } +/* + * Assign new credential to the process, fixing up HRL accounting + * as neccessary. + */ +void +change_cred(struct proc *p, struct ucred *newcred) +{ + PROC_LOCK_ASSERT(p, MA_OWNED); + + hrl_proc_ucred_changing(p, newcred); + p->p_ucred = newcred; +} + /*- * Change a process's effective uid. * Side effects: newcred->cr_uid and newcred->cr_uidinfo will be modified. diff -urNp current/sys/kern/kern_resource.c hrl/sys/kern/kern_resource.c --- current/sys/kern/kern_resource.c 2009-12-18 11:18:01.174305287 +0100 +++ hrl/sys/kern/kern_resource.c 2009-12-18 11:28:32.200305239 +0100 @@ -43,6 +43,7 @@ __FBSDID("$FreeBSD: src/sys/kern/kern_re #include #include #include +#include #include #include #include @@ -71,11 +72,17 @@ static MALLOC_DEFINE(M_UIDINFO, "uidinfo static struct rwlock uihashtbl_lock; static LIST_HEAD(uihashhead, uidinfo) *uihashtbl; static u_long uihash; /* size of hash table - 1 */ +static MALLOC_DEFINE(M_GIDINFO, "gidinfo", "gidinfo structures"); +#define GIHASH(gid) (&gihashtbl[(gid) & gihash]) +static struct rwlock gihashtbl_lock; +static LIST_HEAD(gihashhead, gidinfo) *gihashtbl; +static u_long gihash; /* size of hash table - 1 */ static void calcru1(struct proc *p, struct rusage_ext *ruxp, struct timeval *up, struct timeval *sp); static int donice(struct thread *td, struct proc *chgp, int n); static struct uidinfo *uilookup(uid_t uid); +static struct gidinfo *gilookup(gid_t gid); /* * Resource controls and accounting. @@ -642,6 +649,101 @@ lim_cb(void *arg) callout_reset(&p->p_limco, hz, lim_cb, p); } +static void +hrl_handle_setrlimit(u_int which, struct rlimit *lim, struct thread *td) +{ + int error; + struct hrl_rule *rule, *rule2; + + rule = hrl_rule_alloc(M_WAITOK); + rule->hr_subject_type = HRL_SUBJECT_TYPE_PROCESS; + rule->hr_subject.hs_proc = td->td_proc; + rule->hr_action = HRL_ACTION_DENY; + rule->hr_amount = -1; + rule->hr_per = HRL_SUBJECT_TYPE_PROCESS; + + switch (which) { + case RLIMIT_CPU: + rule->hr_resource = HRL_RESOURCE_CPUTIME; + rule->hr_action = HRL_ACTION_SIGXCPU; + break; + case RLIMIT_FSIZE: + rule->hr_resource = HRL_RESOURCE_FILESIZE; + break; + case RLIMIT_DATA: + rule->hr_resource = HRL_RESOURCE_DATASIZE; + break; + case RLIMIT_STACK: + rule->hr_resource = HRL_RESOURCE_STACKSIZE; + rule->hr_action = HRL_ACTION_SIGSEGV; + break; + case RLIMIT_CORE: + rule->hr_resource = HRL_RESOURCE_COREDUMPSIZE; + break; + case RLIMIT_RSS: + rule->hr_resource = HRL_RESOURCE_MEMORYUSE; + break; + case RLIMIT_MEMLOCK: + rule->hr_resource = HRL_RESOURCE_MEMORYLOCKED; + break; + case RLIMIT_NPROC: + rule->hr_resource = HRL_RESOURCE_MAXPROCESSES; + rule->hr_per = HRL_SUBJECT_TYPE_USER; + break; + case RLIMIT_NOFILE: + rule->hr_resource = HRL_RESOURCE_FILEDESCRIPTORS; + rule->hr_action = HRL_ACTION_SIGXFSZ; + break; + case RLIMIT_SBSIZE: + rule->hr_resource = HRL_RESOURCE_SBSIZE; + rule->hr_per = HRL_SUBJECT_TYPE_USER; + break; + case RLIMIT_VMEM: + rule->hr_resource = HRL_RESOURCE_VMEMORYUSE; + break; + case RLIMIT_NPTS: + rule->hr_resource = HRL_RESOURCE_PTY; + rule->hr_per = HRL_SUBJECT_TYPE_USER; + break; + case RLIMIT_SWAP: + rule->hr_resource = HRL_RESOURCE_SWAP; + rule->hr_per = HRL_SUBJECT_TYPE_USER; + break; + default: + panic("hrl_handle_setrlimit: unknown limit %d", which); + } + + /* + * Remove a previous limit that might exist for the resource + * with a different hr_amount. + */ + hrl_rule_remove(rule); + + /* + * For rules with action different than "deny", we add another + * rule, "deny". + */ + if (rule->hr_action != HRL_ACTION_DENY) { + rule2 = hrl_rule_duplicate(rule, M_WAITOK); + rule2->hr_action = HRL_ACTION_DENY; + hrl_rule_remove(rule2); + + if (lim->rlim_cur != RLIM_INFINITY) { + rule2->hr_amount = lim->rlim_cur; + error = hrl_rule_add(rule2); + KASSERT(error == 0, ("hrl_rule_add failed")); + } + hrl_rule_release(rule2); + } + + if (lim->rlim_cur != RLIM_INFINITY) { + rule->hr_amount = lim->rlim_cur; + error = hrl_rule_add(rule); + KASSERT(error == 0, ("hrl_rule_add failed")); + } + hrl_rule_release(rule); +} + int kern_setrlimit(td, which, limp) struct thread *td; @@ -763,6 +865,8 @@ kern_setrlimit(td, which, limp) } } + hrl_handle_setrlimit(which, alimp, td); + return (0); } @@ -1216,6 +1320,7 @@ uifind(uid) mtx_init(&uip->ui_vmsize_mtx, "ui_vmsize", NULL, MTX_DEF); LIST_INSERT_HEAD(UIHASH(uid), uip, ui_hash); + hrl_container_create(&uip->ui_container); } } uihold(uip); @@ -1263,6 +1368,7 @@ uifree(uip) /* Prepare for suboptimal case. */ rw_wlock(&uihashtbl_lock); if (refcount_release(&uip->ui_ref)) { + hrl_container_destroy(&uip->ui_container); LIST_REMOVE(uip, ui_hash); rw_wunlock(&uihashtbl_lock); if (uip->ui_sbsize != 0) @@ -1285,6 +1391,179 @@ uifree(uip) rw_wunlock(&uihashtbl_lock); } +int +ui_container_foreach(int (*callback)(struct hrl_container *container, + const struct hrl_rule *filter, void *arg3), + const struct hrl_rule *filter, void *arg3) +{ + int error; + struct uidinfo *uip, *nextuip; + struct uihashhead *uih; + + rw_rlock(&uihashtbl_lock); + for (uih = &uihashtbl[uihash]; uih >= uihashtbl; uih--) { + for (uip = LIST_FIRST(uih); uip; uip = nextuip) { + nextuip = LIST_NEXT(uip, ui_hash); + error = (callback)(&uip->ui_container, filter, arg3); + if (error) { + rw_runlock(&uihashtbl_lock); + return (error); + } + } + } + rw_runlock(&uihashtbl_lock); + + return (0); +} + +/* + * Find the gidinfo structure for a gid. This structure is used to + * track the total resource consumption (process count, socket buffer + * size, etc.) for the gid and impose limits. + */ +void +gihashinit() +{ + + gihashtbl = hashinit(maxproc / 16, M_GIDINFO, &gihash); + rw_init(&gihashtbl_lock, "gidinfo hash"); +} + +/* + * Look up a gidinfo struct for the parameter gid. + * gihashtbl_lock must be locked. + */ +static struct gidinfo * +gilookup(gid) + gid_t gid; +{ + struct gihashhead *gipp; + struct gidinfo *gip; + + rw_assert(&gihashtbl_lock, RA_LOCKED); + gipp = GIHASH(gid); + LIST_FOREACH(gip, gipp, gi_hash) + if (gip->gi_gid == gid) + break; + + return (gip); +} + +/* + * Find or allocate a struct gidinfo for a particular gid. + * Increase refcount on gidinfo struct returned. + * gifree() should be called on a struct gidinfo when released. + */ +struct gidinfo * +gifind(gid) + gid_t gid; +{ + struct gidinfo *old_gip, *gip; + + rw_rlock(&gihashtbl_lock); + gip = gilookup(gid); + if (gip == NULL) { + rw_runlock(&gihashtbl_lock); + gip = malloc(sizeof(*gip), M_GIDINFO, M_WAITOK | M_ZERO); + rw_wlock(&gihashtbl_lock); + /* + * There's a chance someone created our gidinfo while we + * were in malloc and not holding the lock, so we have to + * make sure we don't insert a duplicate gidinfo. + */ + if ((old_gip = gilookup(gid)) != NULL) { + /* Someone else beat us to it. */ + free(gip, M_GIDINFO); + gip = old_gip; + } else { + refcount_init(&gip->gi_ref, 0); + gip->gi_gid = gid; + LIST_INSERT_HEAD(GIHASH(gid), gip, gi_hash); + } + } + gihold(gip); + rw_unlock(&gihashtbl_lock); + return (gip); +} + +/* + * Place another refcount on a gidinfo struct. + */ +void +gihold(gip) + struct gidinfo *gip; +{ + + refcount_acquire(&gip->gi_ref); +} + +/*- + * Since gidinfo structs have a long lifetime, we use an + * opportunistic refcounting scheme to avoid locking the lookup hash + * for each release. + * + * If the refcount hits 0, we need to free the structure, + * which means we need to lock the hash. + * Optimal case: + * After locking the struct and lowering the refcount, if we find + * that we don't need to free, simply unlock and return. + * Suboptimal case: + * If refcount lowering results in need to free, bump the count + * back up, lose the lock and acquire the locks in the proper + * order to try again. + */ +void +gifree(gip) + struct gidinfo *gip; +{ + int old; + + /* Prepare for optimal case. */ + old = gip->gi_ref; + if (old > 1 && atomic_cmpset_int(&gip->gi_ref, old, old - 1)) + return; + + /* Prepare for suboptimal case. */ + rw_wlock(&gihashtbl_lock); + if (refcount_release(&gip->gi_ref)) { + hrl_container_destroy(&gip->gi_container); + LIST_REMOVE(gip, gi_hash); + rw_wunlock(&gihashtbl_lock); + free(gip, M_GIDINFO); + return; + } + /* + * Someone added a reference between atomic_cmpset_int() and + * rw_wlock(&gihashtbl_lock). + */ + rw_wunlock(&gihashtbl_lock); +} + +int +gi_container_foreach(int (*callback)(struct hrl_container *container, + const struct hrl_rule *filter, void *arg3), + const struct hrl_rule *filter, void *arg3) +{ + int error; + struct gidinfo *gip, *nextgip; + struct gihashhead *gih; + + rw_rlock(&gihashtbl_lock); + for (gih = &gihashtbl[gihash]; gih >= gihashtbl; gih--) { + for (gip = LIST_FIRST(gih); gip; gip = nextgip) { + nextgip = LIST_NEXT(gip, gi_hash); + error = (callback)(&gip->gi_container, filter, arg3); + if (error) { + rw_runlock(&gihashtbl_lock); + return (error); + } + } + } + rw_runlock(&gihashtbl_lock); + + return (0); +} + /* * Change the count associated with number of processes * a given user is using. When 'max' is 0, don't enforce a limit diff -urNp current/sys/kern/syscalls.c hrl/sys/kern/syscalls.c --- current/sys/kern/syscalls.c 2009-12-18 11:18:02.578146557 +0100 +++ hrl/sys/kern/syscalls.c 2009-12-18 11:28:35.461557996 +0100 @@ -2,8 +2,8 @@ * System call names. * * DO NOT EDIT-- this file is automatically generated. - * $FreeBSD: src/sys/kern/syscalls.c,v 1.238 2009/10/27 11:01:15 kib Exp $ - * created from FreeBSD: head/sys/kern/syscalls.master 198508 2009-10-27 10:55:34Z kib + * $FreeBSD$ + * created from FreeBSD: src/sys/kern/syscalls.master,v 1.261 2009/10/27 10:55:34 kib Exp */ const char *syscallnames[] = { @@ -530,4 +530,11 @@ const char *syscallnames[] = { "#520", /* 520 = pdgetpid */ "#521", /* 521 = pdwait */ "pselect", /* 522 = pselect */ + "getloginclass", /* 523 = getloginclass */ + "setloginclass", /* 524 = setloginclass */ + "hrl_get_usage", /* 525 = hrl_get_usage */ + "hrl_get_rules", /* 526 = hrl_get_rules */ + "hrl_get_limits", /* 527 = hrl_get_limits */ + "hrl_add_rule", /* 528 = hrl_add_rule */ + "hrl_remove_rule", /* 529 = hrl_remove_rule */ }; diff -urNp current/sys/kern/syscalls.master hrl/sys/kern/syscalls.master --- current/sys/kern/syscalls.master 2009-12-18 11:18:02.608394536 +0100 +++ hrl/sys/kern/syscalls.master 2009-12-18 11:28:35.481632335 +0100 @@ -923,5 +923,13 @@ fd_set *ou, fd_set *ex, \ const struct timespec *ts, \ const sigset_t *sm); } +523 AUE_NULL STD { int getloginclass(char *namebuf, size_t \ + namelen); } +524 AUE_NULL STD { int setloginclass(const char *namebuf); } +525 AUE_NULL STD { int hrl_get_usage(const void *inbufp, size_t inbuflen, void *outbufp, size_t outbuflen); } +526 AUE_NULL STD { int hrl_get_rules(const void *inbufp, size_t inbuflen, void *outbufp, size_t outbuflen); } +527 AUE_NULL STD { int hrl_get_limits(const void *inbufp, size_t inbuflen, void *outbufp, size_t outbuflen); } +528 AUE_NULL STD { int hrl_add_rule(const void *inbufp, size_t inbuflen, void *outbufp, size_t outbuflen); } +529 AUE_NULL STD { int hrl_remove_rule(const void *inbufp, size_t inbuflen, void *outbufp, size_t outbuflen); } ; Please copy any additions and changes to the following compatability tables: ; sys/compat/freebsd32/syscalls.master diff -urNp current/sys/kern/tty_pts.c hrl/sys/kern/tty_pts.c --- current/sys/kern/tty_pts.c 2009-12-18 11:18:02.850838198 +0100 +++ hrl/sys/kern/tty_pts.c 2009-12-18 11:28:36.148174312 +0100 @@ -45,6 +45,7 @@ __FBSDID("$FreeBSD: src/sys/kern/tty_pts #include #include #include +#include #include #include #include @@ -706,7 +707,7 @@ static int pts_alloc(int fflags, struct thread *td, struct file *fp) { - int unit, ok; + int unit, ok, error; struct tty *tp; struct pts_softc *psc; struct proc *p = td->td_proc; @@ -715,7 +716,10 @@ pts_alloc(int fflags, struct thread *td, /* Resource limiting. */ PROC_LOCK(p); ok = chgptscnt(uid, 1, lim_cur(p, RLIMIT_NPTS)); + error = hrl_alloc(p, HRL_RESOURCE_PTY, 1); PROC_UNLOCK(p); + if (ok != !error) + printf("pts_alloc: ok = %d, error = %d\n", ok, error); if (!ok) return (EAGAIN); @@ -723,11 +727,13 @@ pts_alloc(int fflags, struct thread *td, unit = alloc_unr(pts_pool); if (unit < 0) { chgptscnt(uid, -1, 0); + hrl_free(p, HRL_RESOURCE_PTY, 1); return (EAGAIN); } if (unit > pts_maxdev) { free_unr(pts_pool, unit); chgptscnt(uid, -1, 0); + hrl_free(p, HRL_RESOURCE_PTY, 1); return (EAGAIN); } @@ -757,7 +763,7 @@ int pts_alloc_external(int fflags, struct thread *td, struct file *fp, struct cdev *dev, const char *name) { - int ok; + int ok, error; struct tty *tp; struct pts_softc *psc; struct proc *p = td->td_proc; @@ -766,7 +772,10 @@ pts_alloc_external(int fflags, struct th /* Resource limiting. */ PROC_LOCK(p); ok = chgptscnt(uid, 1, lim_cur(p, RLIMIT_NPTS)); + error = hrl_alloc(p, HRL_RESOURCE_PTY, 1); PROC_UNLOCK(p); + if (ok != !error) + printf("pts_alloc: ok = %d, error = %d\n", ok, error); if (!ok) return (EAGAIN); diff -urNp current/sys/nfsclient/nfs_bio.c hrl/sys/nfsclient/nfs_bio.c --- current/sys/nfsclient/nfs_bio.c 2009-12-18 11:18:24.210551514 +0100 +++ hrl/sys/nfsclient/nfs_bio.c 2009-12-18 11:29:12.404365532 +0100 @@ -41,6 +41,7 @@ __FBSDID("$FreeBSD: src/sys/nfsclient/nf #include #include #include +#include #include #include #include @@ -951,14 +952,10 @@ flush_and_restart: * file servers have no limits, i don't think it matters */ if (p != NULL) { - PROC_LOCK(p); - if (uio->uio_offset + uio->uio_resid > - lim_cur(p, RLIMIT_FSIZE)) { - psignal(p, SIGXFSZ); - PROC_UNLOCK(p); + error = hrl_allocated(p, HRL_RESOURCE_FILESIZE, + (uoff_t)uio->uio_offset + uio->uio_resid); + if (error) return (EFBIG); - } - PROC_UNLOCK(p); } biosize = vp->v_mount->mnt_stat.f_iosize; diff -urNp current/sys/sys/hrl.h hrl/sys/sys/hrl.h --- current/sys/sys/hrl.h 1970-01-01 01:00:00.000000000 +0100 +++ hrl/sys/sys/hrl.h 2009-12-18 11:29:23.937429930 +0100 @@ -0,0 +1,195 @@ +/*- + * Copyright (c) 2009 Edward Tomasz Napierała + * 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. + * + * $FreeBSD$ + */ + +#ifndef _HRL_H_ +#define _HRL_H_ + +#include +#include +#include + +struct proc; +struct uidinfo; +struct gidinfo; +struct loginclass; +struct prison; +struct ucred; + +/* + * Hierarchical Resource Limits. + */ + +/* + * 'hrl_rule' describes a single limit configured by the system + * administrator or a temporary limit set using setrlimit(2). + * The difference between 'subject' and 'per' is best described + * by example: to specify that every process of user with uid 1984 + * can consume 1gb of virtual memory, the 'hr_subject_type' would be + * HRL_SUBJECT_TYPE_USER, 'hrl_subject.hs_uip' would point to + * 'struct uidinfo' for uid 1984, and 'hr_per' would be equal + * HRL_SUBJECT_TYPE_PROCESS. + * + * 'hr_refcount' is equal to the number of hrl_limit structures + * pointing to the rule. + * + * This structure must never change after being added, via hrl_limit + * structures, to subjects. In order to change a limit, add a new + * rule and remove the previous one. + */ +struct hrl_rule { + int hr_subject_type; +#ifdef DIAGNOSTIC + struct { +#else + union { +#endif + struct proc *hs_proc; + struct uidinfo *hs_uip; + struct gidinfo *hs_gip; + struct loginclass *hs_loginclass; + struct prison *hs_prison; + } hr_subject; + int hr_per; + int hr_resource; + int hr_action; + int64_t hr_amount; + u_int hr_refcount; +}; + +#define HRL_SUBJECT_TYPE_UNDEFINED -1 +#define HRL_SUBJECT_TYPE_PROCESS 0x0000 +#define HRL_SUBJECT_TYPE_USER 0x0001 +#define HRL_SUBJECT_TYPE_GROUP 0x0002 +#define HRL_SUBJECT_TYPE_LOGINCLASS 0x0003 +#define HRL_SUBJECT_TYPE_JAIL 0x0004 +#define HRL_SUBJECT_TYPE_MAX HRL_SUBJECT_TYPE_JAIL + +/* + * 'hr_per' takes the same flags as 'hr_subject_type'. + */ + +#define HRL_RESOURCE_UNDEFINED -1 +#define HRL_RESOURCE_CPUTIME 0x0000 +#define HRL_RESOURCE_FILESIZE 0x0001 +#define HRL_RESOURCE_DATASIZE 0x0002 +#define HRL_RESOURCE_STACKSIZE 0x0003 +#define HRL_RESOURCE_COREDUMPSIZE 0x0004 +#define HRL_RESOURCE_MEMORYUSE 0x0005 +#define HRL_RESOURCE_MEMORYLOCKED 0x0006 +#define HRL_RESOURCE_MAXPROCESSES 0x0007 +#define HRL_RESOURCE_FILEDESCRIPTORS 0x0008 +#define HRL_RESOURCE_SBSIZE 0x0009 +#define HRL_RESOURCE_VMEMORYUSE 0x000a +#define HRL_RESOURCE_PTY 0x000b +#define HRL_RESOURCE_SWAP 0x000c +#define HRL_RESOURCE_MAX HRL_RESOURCE_SWAP + +#define HRL_ACTION_UNDEFINED -1 +#define HRL_ACTION_DENY 0x0000 +#define HRL_ACTION_DELAY 0x0001 +#define HRL_ACTION_LOG 0x0002 +#define HRL_ACTION_SIGHUP 0x0003 +#define HRL_ACTION_SIGINT 0x0004 +#define HRL_ACTION_SIGKILL 0x0005 +#define HRL_ACTION_SIGSEGV 0x0006 +#define HRL_ACTION_SIGXCPU 0x0007 +#define HRL_ACTION_SIGXFSZ 0x0008 +#define HRL_ACTION_MAX HRL_ACTION_SIGXFSZ + +#define HRL_AMOUNT_UNDEFINED -1 + +/* + * Processes may have at most three parent containers - prison, uidinfo, + * and loginclass. Other subjects have less - struct prison may have only + * one parent container, loginclass and uidinfo structures have none. + * This may change when - and if - we add per-group resource limits. + */ +#define HRL_HC_PARENTS_MAX 3 + +/* + * 'hrl_container' defines resource consumption for a particular + * subject, such as process or jail. Containers form a graph - each + * container has zero or more subcontainers and zero or more + * "containing" containers (parents). For example, container for + * an uidinfo can have several subcontainers for processes of that + * user. On the other hand, each process can have several containing + * containers - one for jail the process is in, one for the user, + * one for every group this process belongs to (note that per-group + * limits are not implemented yet). + * + * Every process has exactly one container assigned to it. Containers + * for other objects are initialized when there is a rule which requires + * it. For example, uidinfo will have container assigned only if there + * is a rule this uidinfo is subject to, and 'hr_per' for this rule + * is HRL_SUBJECT_TYPE_USER. + * + * This structure must be filled with zeroes initially. + */ +struct hrl_container { + int64_t hc_resources[HRL_RESOURCE_MAX + 1]; + struct hrl_container *hc_parents[HRL_HC_PARENTS_MAX + 1]; + LIST_HEAD(, hrl_limit) hc_limits; +}; + +#ifdef _KERNEL + +int hrl_alloc(struct proc *p, int object, uint64_t amount); +int hrl_allocated(struct proc *p, int object, uint64_t amount); +void hrl_free(struct proc *p, int object, uint64_t amount); + +void hrl_proc_exiting(struct proc *p); + +void hrl_proc_init(struct proc *p); +void hrl_proc_ucred_changing(struct proc *p, struct ucred *newcred); + +struct hrl_rule *hrl_rule_alloc(int flags); +struct hrl_rule *hrl_rule_duplicate(const struct hrl_rule *rule, int flags); +void hrl_rule_acquire(struct hrl_rule *rule); +void hrl_rule_release(struct hrl_rule *rule); +int hrl_rule_add(struct hrl_rule *rule); +int hrl_rule_remove(const struct hrl_rule *filter); + +void hrl_container_create(struct hrl_container *container); +void hrl_container_destroy(struct hrl_container *container); + +#else /* !_KERNEL */ + +/* + * Syscall interface. + */ +__BEGIN_DECLS +int hrl_get_usage(const char *inbufp, size_t inbuflen, char *outbufp, size_t outbuflen); +int hrl_get_rules(const char *inbufp, size_t inbuflen, char *outbufp, size_t outbuflen); +int hrl_get_limits(const char *inbufp, size_t inbuflen, char *outbufp, size_t outbuflen); +int hrl_add_rule(const char *inbufp, size_t inbuflen, char *outbufp, size_t outbuflen); +int hrl_remove_rule(const char *inbufp, size_t inbuflen, char *outbufp, size_t outbuflen); +__END_DECLS + +#endif /* !_KERNEL */ + +#endif /* !_HRL_H_ */ diff -urNp current/sys/sys/jail.h hrl/sys/sys/jail.h --- current/sys/sys/jail.h 2009-12-18 11:18:32.158522555 +0100 +++ hrl/sys/sys/jail.h 2009-12-18 11:29:23.998041823 +0100 @@ -30,6 +30,8 @@ #ifndef _SYS_JAIL_H_ #define _SYS_JAIL_H_ +#include + #ifdef _KERNEL struct jail_v0 { u_int32_t version; @@ -179,6 +181,7 @@ struct prison { char pr_hostname[MAXHOSTNAMELEN]; /* (p) jail hostname */ char pr_domainname[MAXHOSTNAMELEN]; /* (p) jail domainname */ char pr_hostuuid[HOSTUUIDLEN]; /* (p) jail hostuuid */ + struct hrl_container pr_container; /* (*) HRL resource accounting */ }; #endif /* _KERNEL || _WANT_PRISON */ diff -urNp current/sys/sys/loginclass.h hrl/sys/sys/loginclass.h --- current/sys/sys/loginclass.h 1970-01-01 01:00:00.000000000 +0100 +++ hrl/sys/sys/loginclass.h 2009-12-18 11:29:24.210108441 +0100 @@ -0,0 +1,50 @@ +/*- + * Copyright (c) 2009 Edward Tomasz Napierała + * 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. + */ + +#ifndef _SYS_LOGINCLASS_H_ +#define _SYS_LOGINCLASS_H_ + +#include + +/* + * Exactly one of these structures exists per login class. + */ +struct loginclass { + LIST_ENTRY(loginclass) lc_next; + char lc_name[MAXLOGNAME]; + u_int lc_refcount; + struct hrl_container lc_container; +}; + +void loginclass_acquire(struct loginclass *lc); +void loginclass_release(struct loginclass *lc); +struct loginclass *loginclass_find(const char *name); +int loginclass_container_foreach(int (*callback)(struct hrl_container *container, + const struct hrl_rule *filter, void *arg3), + const struct hrl_rule *filter, void *arg3); + +#endif /* !_SYS_LOGINCLASS_H_ */ + diff -urNp current/sys/sys/priv.h hrl/sys/sys/priv.h --- current/sys/sys/priv.h 2009-12-18 11:18:32.572655636 +0100 +++ hrl/sys/sys/priv.h 2009-12-18 11:29:24.634328279 +0100 @@ -156,6 +156,7 @@ #define PRIV_PROC_LIMIT 160 /* Exceed user process limit. */ #define PRIV_PROC_SETLOGIN 161 /* Can call setlogin. */ #define PRIV_PROC_SETRLIMIT 162 /* Can raise resources limits. */ +#define PRIV_PROC_SETLOGINCLASS 163 /* Can call setloginclass(2). */ /* System V IPC privileges. */ @@ -482,9 +483,15 @@ #define PRIV_AFS_DAEMON 661 /* Can become the AFS daemon. */ /* + * Hierarchical Resource Limits privileges. + */ +#define PRIV_HRL_SET 670 +#define PRIV_HRL_GET 671 + +/* * Track end of privilege list. */ -#define _PRIV_HIGHEST 662 +#define _PRIV_HIGHEST 672 /* * Validate that a named privilege is known by the privilege system. Invalid diff -urNp current/sys/sys/proc.h hrl/sys/sys/proc.h --- current/sys/sys/proc.h 2009-12-18 11:18:32.612978638 +0100 +++ hrl/sys/sys/proc.h 2009-12-18 11:29:24.674617477 +0100 @@ -44,6 +44,7 @@ #ifndef _KERNEL #include #endif +#include #include #include #include @@ -510,6 +511,7 @@ struct proc { int p_boundary_count;/* (c) Num threads at user boundary */ int p_pendingcnt; /* how many signals are pending */ struct itimers *p_itimers; /* (c) POSIX interval timers. */ + struct hrl_container p_container; /* (*) HRL resource accounting */ /* End area that is zeroed on creation. */ #define p_endzero p_magic diff -urNp current/sys/sys/resourcevar.h hrl/sys/sys/resourcevar.h --- current/sys/sys/resourcevar.h 2009-12-18 11:18:32.663507965 +0100 +++ hrl/sys/sys/resourcevar.h 2009-12-18 11:29:24.785824907 +0100 @@ -38,6 +38,7 @@ #ifdef _KERNEL #include #include +#include #endif /* @@ -97,11 +98,27 @@ struct uidinfo { long ui_ptscnt; /* (b) number of pseudo-terminals */ uid_t ui_uid; /* (a) uid */ u_int ui_ref; /* (b) reference count */ + struct hrl_container ui_container; /* (*) HRL resource accounting */ }; #define UIDINFO_VMSIZE_LOCK(ui) mtx_lock(&((ui)->ui_vmsize_mtx)) #define UIDINFO_VMSIZE_UNLOCK(ui) mtx_unlock(&((ui)->ui_vmsize_mtx)) +/* + * Per gid resource consumption + * + * Locking guide: + * (a) Constant from inception + * (b) Lockless, updated using atomics + * (c) Locked by global uihashtbl_mtx + */ +struct gidinfo { + LIST_ENTRY(gidinfo) gi_hash; /* (c) hash chain of gidinfos */ + gid_t gi_gid; /* (a) gid */ + u_int gi_ref; /* (b) reference count */ + struct hrl_container gi_container; /* (*) HRL resource accounting */ +}; + struct proc; struct rusage_ext; struct thread; @@ -138,6 +155,17 @@ struct uidinfo void uifree(struct uidinfo *uip); void uihashinit(void); void uihold(struct uidinfo *uip); +int ui_container_foreach(int (*callback)(struct hrl_container *container, + const struct hrl_rule *filter, void *arg3), + const struct hrl_rule *filter, void *arg3); +struct gidinfo + *gifind(gid_t gid); +void gifree(struct gidinfo *gip); +void gihashinit(void); +void gihold(struct gidinfo *gip); +int gi_container_foreach(int (*callback)(struct hrl_container *container, + const struct hrl_rule *filter, void *arg3), + const struct hrl_rule *filter, void *arg3); #endif /* _KERNEL */ #endif /* !_SYS_RESOURCEVAR_H_ */ diff -urNp current/sys/sys/syscall.h hrl/sys/sys/syscall.h --- current/sys/sys/syscall.h 2009-12-18 11:18:33.016929419 +0100 +++ hrl/sys/sys/syscall.h 2009-12-18 11:29:25.139278768 +0100 @@ -2,8 +2,8 @@ * System call numbers. * * DO NOT EDIT-- this file is automatically generated. - * $FreeBSD: src/sys/sys/syscall.h,v 1.235 2009/10/27 11:01:40 kib Exp $ - * created from FreeBSD: head/sys/kern/syscalls.master 198508 2009-10-27 10:55:34Z kib + * $FreeBSD$ + * created from FreeBSD: src/sys/kern/syscalls.master,v 1.261 2009/10/27 10:55:34 kib Exp */ #define SYS_syscall 0 @@ -429,4 +429,11 @@ #define SYS_shmctl 512 #define SYS_lpathconf 513 #define SYS_pselect 522 -#define SYS_MAXSYSCALL 523 +#define SYS_getloginclass 523 +#define SYS_setloginclass 524 +#define SYS_hrl_get_usage 525 +#define SYS_hrl_get_rules 526 +#define SYS_hrl_get_limits 527 +#define SYS_hrl_add_rule 528 +#define SYS_hrl_remove_rule 529 +#define SYS_MAXSYSCALL 530 diff -urNp current/sys/sys/syscall.mk hrl/sys/sys/syscall.mk --- current/sys/sys/syscall.mk 2009-12-18 11:18:33.016929419 +0100 +++ hrl/sys/sys/syscall.mk 2009-12-18 11:29:25.189711156 +0100 @@ -1,7 +1,7 @@ # FreeBSD system call names. # DO NOT EDIT-- this file is automatically generated. -# $FreeBSD: src/sys/sys/syscall.mk,v 1.190 2009/10/27 11:01:40 kib Exp $ -# created from FreeBSD: head/sys/kern/syscalls.master 198508 2009-10-27 10:55:34Z kib +# $FreeBSD$ +# created from FreeBSD: src/sys/kern/syscalls.master,v 1.261 2009/10/27 10:55:34 kib Exp MIASM = \ syscall.o \ exit.o \ @@ -377,4 +377,11 @@ MIASM = \ msgctl.o \ shmctl.o \ lpathconf.o \ - pselect.o + pselect.o \ + getloginclass.o \ + setloginclass.o \ + hrl_get_usage.o \ + hrl_get_rules.o \ + hrl_get_limits.o \ + hrl_add_rule.o \ + hrl_remove_rule.o diff -urNp current/sys/sys/sysproto.h hrl/sys/sys/sysproto.h --- current/sys/sys/sysproto.h 2009-12-18 11:18:33.067316270 +0100 +++ hrl/sys/sys/sysproto.h 2009-12-18 11:29:25.300741747 +0100 @@ -2,8 +2,8 @@ * System call prototypes. * * DO NOT EDIT-- this file is automatically generated. - * $FreeBSD: src/sys/sys/sysproto.h,v 1.242 2009/12/16 21:53:56 imp Exp $ - * created from FreeBSD: head/sys/kern/syscalls.master 198508 2009-10-27 10:55:34Z kib + * $FreeBSD$ + * created from FreeBSD: src/sys/kern/syscalls.master,v 1.261 2009/10/27 10:55:34 kib Exp */ #ifndef _SYS_SYSPROTO_H_ @@ -1649,6 +1649,43 @@ struct pselect_args { char ts_l_[PADL_(const struct timespec *)]; const struct timespec * ts; char ts_r_[PADR_(const struct timespec *)]; char sm_l_[PADL_(const sigset_t *)]; const sigset_t * sm; char sm_r_[PADR_(const sigset_t *)]; }; +struct getloginclass_args { + char namebuf_l_[PADL_(char *)]; char * namebuf; char namebuf_r_[PADR_(char *)]; + char namelen_l_[PADL_(size_t)]; size_t namelen; char namelen_r_[PADR_(size_t)]; +}; +struct setloginclass_args { + char namebuf_l_[PADL_(const char *)]; const char * namebuf; char namebuf_r_[PADR_(const char *)]; +}; +struct hrl_get_usage_args { + char inbufp_l_[PADL_(const void *)]; const void * inbufp; char inbufp_r_[PADR_(const void *)]; + char inbuflen_l_[PADL_(size_t)]; size_t inbuflen; char inbuflen_r_[PADR_(size_t)]; + char outbufp_l_[PADL_(void *)]; void * outbufp; char outbufp_r_[PADR_(void *)]; + char outbuflen_l_[PADL_(size_t)]; size_t outbuflen; char outbuflen_r_[PADR_(size_t)]; +}; +struct hrl_get_rules_args { + char inbufp_l_[PADL_(const void *)]; const void * inbufp; char inbufp_r_[PADR_(const void *)]; + char inbuflen_l_[PADL_(size_t)]; size_t inbuflen; char inbuflen_r_[PADR_(size_t)]; + char outbufp_l_[PADL_(void *)]; void * outbufp; char outbufp_r_[PADR_(void *)]; + char outbuflen_l_[PADL_(size_t)]; size_t outbuflen; char outbuflen_r_[PADR_(size_t)]; +}; +struct hrl_get_limits_args { + char inbufp_l_[PADL_(const void *)]; const void * inbufp; char inbufp_r_[PADR_(const void *)]; + char inbuflen_l_[PADL_(size_t)]; size_t inbuflen; char inbuflen_r_[PADR_(size_t)]; + char outbufp_l_[PADL_(void *)]; void * outbufp; char outbufp_r_[PADR_(void *)]; + char outbuflen_l_[PADL_(size_t)]; size_t outbuflen; char outbuflen_r_[PADR_(size_t)]; +}; +struct hrl_add_rule_args { + char inbufp_l_[PADL_(const void *)]; const void * inbufp; char inbufp_r_[PADR_(const void *)]; + char inbuflen_l_[PADL_(size_t)]; size_t inbuflen; char inbuflen_r_[PADR_(size_t)]; + char outbufp_l_[PADL_(void *)]; void * outbufp; char outbufp_r_[PADR_(void *)]; + char outbuflen_l_[PADL_(size_t)]; size_t outbuflen; char outbuflen_r_[PADR_(size_t)]; +}; +struct hrl_remove_rule_args { + char inbufp_l_[PADL_(const void *)]; const void * inbufp; char inbufp_r_[PADR_(const void *)]; + char inbuflen_l_[PADL_(size_t)]; size_t inbuflen; char inbuflen_r_[PADR_(size_t)]; + char outbufp_l_[PADL_(void *)]; void * outbufp; char outbufp_r_[PADR_(void *)]; + char outbuflen_l_[PADL_(size_t)]; size_t outbuflen; char outbuflen_r_[PADR_(size_t)]; +}; int nosys(struct thread *, struct nosys_args *); void sys_exit(struct thread *, struct sys_exit_args *); int fork(struct thread *, struct fork_args *); @@ -2008,6 +2045,13 @@ int msgctl(struct thread *, struct msgct int shmctl(struct thread *, struct shmctl_args *); int lpathconf(struct thread *, struct lpathconf_args *); int pselect(struct thread *, struct pselect_args *); +int getloginclass(struct thread *, struct getloginclass_args *); +int setloginclass(struct thread *, struct setloginclass_args *); +int hrl_get_usage(struct thread *, struct hrl_get_usage_args *); +int hrl_get_rules(struct thread *, struct hrl_get_rules_args *); +int hrl_get_limits(struct thread *, struct hrl_get_limits_args *); +int hrl_add_rule(struct thread *, struct hrl_add_rule_args *); +int hrl_remove_rule(struct thread *, struct hrl_remove_rule_args *); #ifdef COMPAT_43 @@ -2681,6 +2725,13 @@ int freebsd7_shmctl(struct thread *, str #define SYS_AUE_shmctl AUE_SHMCTL #define SYS_AUE_lpathconf AUE_LPATHCONF #define SYS_AUE_pselect AUE_SELECT +#define SYS_AUE_getloginclass AUE_NULL +#define SYS_AUE_setloginclass AUE_NULL +#define SYS_AUE_hrl_get_usage AUE_NULL +#define SYS_AUE_hrl_get_rules AUE_NULL +#define SYS_AUE_hrl_get_limits AUE_NULL +#define SYS_AUE_hrl_add_rule AUE_NULL +#define SYS_AUE_hrl_remove_rule AUE_NULL #undef PAD_ #undef PADL_ diff -urNp current/sys/sys/ucred.h hrl/sys/sys/ucred.h --- current/sys/sys/ucred.h 2009-12-18 11:18:33.219695413 +0100 +++ hrl/sys/sys/ucred.h 2009-12-18 11:29:25.492682622 +0100 @@ -35,6 +35,8 @@ #include +struct loginclass; + /* * Credentials. * @@ -54,7 +56,7 @@ struct ucred { struct uidinfo *cr_uidinfo; /* per euid resource consumption */ struct uidinfo *cr_ruidinfo; /* per ruid resource consumption */ struct prison *cr_prison; /* jail(2) */ - void *cr_pspare; /* general use */ + struct loginclass *cr_loginclass; /* login class */ u_int cr_flags; /* credential flags */ void *cr_pspare2[2]; /* general use 2 */ #define cr_endcopy cr_label @@ -87,7 +89,9 @@ struct xucred { #ifdef _KERNEL struct proc; struct thread; +struct proc; +void change_cred(struct proc *p, struct ucred *newcred); void change_egid(struct ucred *newcred, gid_t egid); void change_euid(struct ucred *newcred, struct uidinfo *euip); void change_rgid(struct ucred *newcred, gid_t rgid); diff -urNp current/sys/ufs/ffs/ffs_vnops.c hrl/sys/ufs/ffs/ffs_vnops.c --- current/sys/ufs/ffs/ffs_vnops.c 2009-12-18 11:18:33.885546520 +0100 +++ hrl/sys/ufs/ffs/ffs_vnops.c 2009-12-18 11:29:26.381517097 +0100 @@ -70,6 +70,7 @@ __FBSDID("$FreeBSD: src/sys/ufs/ffs/ffs_ #include #include #include +#include #include #include #include @@ -705,14 +706,10 @@ ffs_write(ap) */ td = uio->uio_td; if (vp->v_type == VREG && td != NULL) { - PROC_LOCK(td->td_proc); - if (uio->uio_offset + uio->uio_resid > - lim_cur(td->td_proc, RLIMIT_FSIZE)) { - psignal(td->td_proc, SIGXFSZ); - PROC_UNLOCK(td->td_proc); + error = hrl_allocated(td->td_proc, HRL_RESOURCE_FILESIZE, + (uoff_t)uio->uio_offset + uio->uio_resid); + if (error) return (EFBIG); - } - PROC_UNLOCK(td->td_proc); } resid = uio->uio_resid; diff -urNp current/sys/vm/vm_map.c hrl/sys/vm/vm_map.c --- current/sys/vm/vm_map.c 2009-12-18 11:18:34.541807302 +0100 +++ hrl/sys/vm/vm_map.c 2009-12-18 11:29:27.280176564 +0100 @@ -67,6 +67,7 @@ __FBSDID("$FreeBSD: src/sys/vm/vm_map.c, #include #include +#include #include #include #include @@ -414,6 +415,11 @@ vmspace_exit(struct thread *td) pmap_activate(td); vmspace_dofree(vm); } + hrl_allocated(p, HRL_RESOURCE_DATASIZE, 0); + hrl_allocated(p, HRL_RESOURCE_STACKSIZE, 0); + hrl_allocated(p, HRL_RESOURCE_MEMORYUSE, 0); + hrl_allocated(p, HRL_RESOURCE_MEMORYLOCKED, 0); + hrl_allocated(p, HRL_RESOURCE_VMEMORYUSE, 0); } /* Acquire reference to vmspace owned by another process. */ diff -urNp current/sys/vm/vm_unix.c hrl/sys/vm/vm_unix.c --- current/sys/vm/vm_unix.c 2009-12-18 11:18:34.774094087 +0100 +++ hrl/sys/vm/vm_unix.c 2009-12-18 11:29:27.613588212 +0100 @@ -44,6 +44,7 @@ __FBSDID("$FreeBSD: src/sys/vm/vm_unix.c,v 1.49 2009/04/11 22:34:08 alc Exp $"); #include +#include #include #include #include @@ -73,16 +74,10 @@ obreak(td, uap) { struct vmspace *vm = td->td_proc->p_vmspace; vm_offset_t new, old, base; - rlim_t datalim, vmemlim; int rv; int error = 0; boolean_t do_map_wirefuture; - PROC_LOCK(td->td_proc); - datalim = lim_cur(td->td_proc, RLIMIT_DATA); - vmemlim = lim_cur(td->td_proc, RLIMIT_VMEM); - PROC_UNLOCK(td->td_proc); - do_map_wirefuture = FALSE; new = round_page((vm_offset_t)uap->nsize); vm_map_lock(&vm->vm_map); @@ -90,11 +85,9 @@ obreak(td, uap) base = round_page((vm_offset_t) vm->vm_daddr); old = base + ctob(vm->vm_dsize); if (new > base) { - /* - * Check the resource limit, but allow a process to reduce - * its usage, even if it remains over the limit. - */ - if (new - base > datalim && new > old) { + error = hrl_allocated(td->td_proc, HRL_RESOURCE_DATASIZE, + new - base); + if (error) { error = ENOMEM; goto done; } @@ -112,7 +105,9 @@ obreak(td, uap) goto done; } if (new > old) { - if (vm->vm_map.size + (new - old) > vmemlim) { + error = hrl_allocated(td->td_proc, HRL_RESOURCE_VMEMORYUSE, + vm->vm_map.size + (new - old)); + if (error) { error = ENOMEM; goto done; } diff -urNp current/tools/regression/hrl/00.t hrl/tools/regression/hrl/00.t --- current/tools/regression/hrl/00.t 1970-01-01 01:00:00.000000000 +0100 +++ hrl/tools/regression/hrl/00.t 2009-12-18 11:29:32.380727053 +0100 @@ -0,0 +1,22 @@ +#!/bin/sh +# +# This is a wrapper script to run hrl-rules.test. +# +# $FreeBSD$ +# + +echo "1..2" + +if [ `whoami` != "root" ]; then + echo "not ok 1 - you need to be root to run this test." + exit 1 +fi + +perl $TESTDIR/run $TESTDIR/tools-posix.test > /dev/null + +if [ $? -eq 0 ]; then + echo "ok 2" +else + echo "not ok 2" +fi + diff -urNp current/tools/regression/hrl/run hrl/tools/regression/hrl/run --- current/tools/regression/hrl/run 1970-01-01 01:00:00.000000000 +0100 +++ hrl/tools/regression/hrl/run 2009-12-18 11:29:32.380727053 +0100 @@ -0,0 +1,327 @@ +#!/usr/bin/perl -w -U + +# Copyright (c) 2007, 2008 Andreas Gruenbacher. +# 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, +# without modification, immediately at the beginning of the file. +# 2. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# Alternatively, this software may be distributed under the terms of the +# GNU Public License ("GPL"). +# +# 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. +# +# $FreeBSD: head/tools/regression/acltools/run 185304 2008-11-25 18:29:33Z trasz $ +# + +# +# Possible improvements: +# +# - distinguish stdout and stderr output +# - add environment variable like assignments +# - run up to a specific line +# - resume at a specific line +# + +use strict; +use FileHandle; +use Getopt::Std; +use POSIX qw(isatty setuid getcwd); +use vars qw($opt_l $opt_v); + +no warnings qw(taint); + +$opt_l = ~0; # a really huge number +getopts('l:v'); + +my ($OK, $FAILED) = ("ok", "failed"); +if (isatty(fileno(STDOUT))) { + $OK = "\033[32m" . $OK . "\033[m"; + $FAILED = "\033[31m\033[1m" . $FAILED . "\033[m"; +} + +sub exec_test($$); +sub process_test($$$$); + +my ($prog, $in, $out) = ([], [], []); +my $prog_line = 0; +my ($tests, $failed) = (0,0); +my $lineno; +my $width = ($ENV{COLUMNS} || 80) >> 1; + +for (;;) { + my $line = <>; $lineno++; + if (defined $line) { + # Substitute %VAR and %{VAR} with environment variables. + $line =~ s[%(\w+)][$ENV{$1}]eg; + $line =~ s[%{(\w+)}][$ENV{$1}]eg; + } + if (defined $line) { + if ($line =~ s/^\s*< ?//) { + push @$in, $line; + } elsif ($line =~ s/^\s*> ?//) { + push @$out, $line; + } else { + process_test($prog, $prog_line, $in, $out); + last if $prog_line >= $opt_l; + + $prog = []; + $prog_line = 0; + } + if ($line =~ s/^\s*\$ ?//) { + $prog = [ map { s/\\(.)/$1/g; $_ } split /(? @$result) ? @$out : @$result; + for (my $n=0; $n < $nmax; $n++) { + my $use_re; + if (defined $out->[$n] && $out->[$n] =~ /^~ /) { + $use_re = 1; + $out->[$n] =~ s/^~ //g; + } + + if (!defined($out->[$n]) || !defined($result->[$n]) || + (!$use_re && $result->[$n] ne $out->[$n]) || + ( $use_re && $result->[$n] !~ /^$out->[$n]/)) { + push @good, ($use_re ? '!~' : '!='); + } + else { + push @good, ($use_re ? '=~' : '=='); + } + } + my $good = !(grep /!/, @good); + $tests++; + $failed++ unless $good; + print $good ? $OK : $FAILED, "\n"; + if (!$good || $opt_v) { + for (my $n=0; $n < $nmax; $n++) { + my $l = defined($out->[$n]) ? $out->[$n] : "~"; + chomp $l; + my $r = defined($result->[$n]) ? $result->[$n] : "~"; + chomp $r; + print sprintf("%-" . ($width-3) . "s %s %s\n", + $r, $good[$n], $l); + } + } +} + + +sub su($) { + my ($user) = @_; + + $user ||= "root"; + + my ($login, $pass, $uid, $gid) = getpwnam($user) + or return [ "su: user $user does not exist\n" ]; + my @groups = (); + my $fh = new FileHandle("/etc/group") + or return [ "opening /etc/group: $!\n" ]; + while (<$fh>) { + chomp; + my ($group, $passwd, $gid, $users) = split /:/; + foreach my $u (split /,/, $users) { + push @groups, $gid + if ($user eq $u); + } + } + $fh->close; + + my $groups = join(" ", ($gid, $gid, @groups)); + #print STDERR "[[$groups]]\n"; + $! = 0; # reset errno + $> = 0; + $( = $gid; + $) = $groups; + if ($!) { + return [ "su: $!\n" ]; + } + if ($uid != 0) { + $> = $uid; + #$< = $uid; + if ($!) { + return [ "su: $prog->[1]: $!\n" ]; + } + } + #print STDERR "[($>,$<)($(,$))]"; + return []; +} + + +sub sg($) { + my ($group) = @_; + + my $gid = getgrnam($group) + or return [ "sg: group $group does not exist\n" ]; + my %groups = map { $_ eq $gid ? () : ($_ => 1) } (split /\s/, $)); + + #print STDERR "<<", join("/", keys %groups), ">>\n"; + my $groups = join(" ", ($gid, $gid, keys %groups)); + #print STDERR "[[$groups]]\n"; + $! = 0; # reset errno + if ($> != 0) { + my $uid = $>; + $> = 0; + $( = $gid; + $) = $groups; + $> = $uid; + } else { + $( = $gid; + $) = $groups; + } + if ($!) { + return [ "sg: $!\n" ]; + } + print STDERR "[($>,$<)($(,$))]"; + return []; +} + + +sub exec_test($$) { + my ($prog, $in) = @_; + local (*IN, *IN_DUP, *IN2, *OUT_DUP, *OUT, *OUT2); + my $needs_shell = (join('', @$prog) =~ /[][|<>"'`\$\*\?]/); + + if ($prog->[0] eq "umask") { + umask oct $prog->[1]; + return []; + } elsif ($prog->[0] eq "cd") { + if (!chdir $prog->[1]) { + return [ "chdir: $prog->[1]: $!\n" ]; + } + $ENV{PWD} = getcwd; + return []; + } elsif ($prog->[0] eq "su") { + return su($prog->[1]); + } elsif ($prog->[0] eq "sg") { + return sg($prog->[1]); + } elsif ($prog->[0] eq "export") { + my ($name, $value) = split /=/, $prog->[1]; + # FIXME: need to evaluate $value, so that things like this will work: + # export dir=$PWD/dir + $ENV{$name} = $value; + return []; + } elsif ($prog->[0] eq "unset") { + delete $ENV{$prog->[1]}; + return []; + } + + pipe *IN2, *OUT + or die "Can't create pipe for reading: $!"; + open *IN_DUP, "<&STDIN" + or *IN_DUP = undef; + open *STDIN, "<&IN2" + or die "Can't duplicate pipe for reading: $!"; + close *IN2; + + open *OUT_DUP, ">&STDOUT" + or die "Can't duplicate STDOUT: $!"; + pipe *IN, *OUT2 + or die "Can't create pipe for writing: $!"; + open *STDOUT, ">&OUT2" + or die "Can't duplicate pipe for writing: $!"; + close *OUT2; + + *STDOUT->autoflush(); + *OUT->autoflush(); + + if (fork()) { + # Server + if (*IN_DUP) { + open *STDIN, "<&IN_DUP" + or die "Can't duplicate STDIN: $!"; + close *IN_DUP + or die "Can't close STDIN duplicate: $!"; + } + open *STDOUT, ">&OUT_DUP" + or die "Can't duplicate STDOUT: $!"; + close *OUT_DUP + or die "Can't close STDOUT duplicate: $!"; + + foreach my $line (@$in) { + #print "> $line"; + print OUT $line; + } + close *OUT + or die "Can't close pipe for writing: $!"; + + my $result = []; + while () { + #print "< $_"; + if ($needs_shell) { + s#^/bin/sh: line \d+: ##; + } + push @$result, $_; + } + return $result; + } else { + # Client + $< = $>; + close IN + or die "Can't close read end for input pipe: $!"; + close OUT + or die "Can't close write end for output pipe: $!"; + close OUT_DUP + or die "Can't close STDOUT duplicate: $!"; + local *ERR_DUP; + open ERR_DUP, ">&STDERR" + or die "Can't duplicate STDERR: $!"; + open STDERR, ">&STDOUT" + or die "Can't join STDOUT and STDERR: $!"; + + if ($needs_shell) { + exec ('/bin/sh', '-c', join(" ", @$prog)); + } else { + exec @$prog; + } + print STDERR $prog->[0], ": $!\n"; + exit; + } +} + diff -urNp current/usr.bin/id/id.1 hrl/usr.bin/id/id.1 --- current/usr.bin/id/id.1 2009-12-18 11:18:49.620730461 +0100 +++ hrl/usr.bin/id/id.1 2009-12-18 11:29:43.116582308 +0100 @@ -55,6 +55,8 @@ .Fl P .Op Ar user .Nm +.Fl c +.Nm .Fl g Op Fl nr .Op Ar user .Nm @@ -93,6 +95,8 @@ Display the id as a password file entry. Ignored for compatibility with other .Nm implementations. +.It Fl c +Display current login class. .It Fl g Display the effective group ID as a number. .It Fl n diff -urNp current/usr.bin/id/id.c hrl/usr.bin/id/id.c --- current/usr.bin/id/id.c 2009-12-18 11:18:49.632442005 +0100 +++ hrl/usr.bin/id/id.c 2009-12-18 11:29:43.116582308 +0100 @@ -78,11 +78,13 @@ main(int argc, char *argv[]) struct group *gr; struct passwd *pw; int Gflag, Mflag, Pflag, ch, gflag, id, nflag, pflag, rflag, uflag; - int Aflag; + int Aflag, cflag; + int error; const char *myname; + char loginclass[MAXLOGNAME]; Gflag = Mflag = Pflag = gflag = nflag = pflag = rflag = uflag = 0; - Aflag = 0; + Aflag = cflag = 0; myname = strrchr(argv[0], '/'); myname = (myname != NULL) ? myname + 1 : argv[0]; @@ -96,7 +98,7 @@ main(int argc, char *argv[]) } while ((ch = getopt(argc, argv, - (isgroups || iswhoami) ? "" : "APGMagnpru")) != -1) + (isgroups || iswhoami) ? "" : "APGMacgnpru")) != -1) switch(ch) { #ifdef USE_BSM_AUDIT case 'A': @@ -114,6 +116,9 @@ main(int argc, char *argv[]) break; case 'a': break; + case 'c': + cflag = 1; + break; case 'g': gflag = 1; break; @@ -162,6 +167,14 @@ main(int argc, char *argv[]) } #endif + if (cflag) { + error = getloginclass(loginclass, sizeof(loginclass)); + if (error) + err(1, "loginclass"); + (void)printf("%s\n", loginclass); + exit(0); + } + if (gflag) { id = pw ? pw->pw_gid : rflag ? getgid() : getegid(); if (nflag && (gr = getgrgid(id))) @@ -471,7 +484,7 @@ usage(void) else if (iswhoami) (void)fprintf(stderr, "usage: whoami\n"); else - (void)fprintf(stderr, "%s\n%s%s\n%s\n%s\n%s\n%s\n%s\n", + (void)fprintf(stderr, "%s\n%s%s\n%s\n%s\n%s\n%s\n%s\n%s\n", "usage: id [user]", #ifdef USE_BSM_AUDIT " id -A\n", @@ -481,6 +494,7 @@ usage(void) " id -G [-n] [user]", " id -M", " id -P [user]", + " id -c", " id -g [-nr] [user]", " id -p [user]", " id -u [-nr] [user]"); diff -urNp current/usr.sbin/Makefile hrl/usr.sbin/Makefile --- current/usr.sbin/Makefile 2009-12-18 11:18:56.164322175 +0100 +++ hrl/usr.sbin/Makefile 2009-12-18 11:29:52.578670087 +0100 @@ -67,6 +67,7 @@ SUBDIR= ${_ac} \ getpmac \ gstat \ ${_gssd} \ + hrl \ i2c \ ifmcstat \ inetd \ diff -urNp current/usr.sbin/hrl/Makefile hrl/usr.sbin/hrl/Makefile --- current/usr.sbin/hrl/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ hrl/usr.sbin/hrl/Makefile 2009-12-18 11:30:00.163127609 +0100 @@ -0,0 +1,8 @@ +# $FreeBSD$ + +PROG= hrl +MAN= hrl.8 + +WARNS?= 6 + +.include diff -urNp current/usr.sbin/hrl/hrl.8 hrl/usr.sbin/hrl/hrl.8 --- current/usr.sbin/hrl/hrl.8 1970-01-01 01:00:00.000000000 +0100 +++ hrl/usr.sbin/hrl/hrl.8 2009-12-18 11:30:00.163127609 +0100 @@ -0,0 +1,127 @@ +.\"- +.\" Copyright (c) 2009 Edward Tomasz Napierala +.\" 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 THE VOICES IN HIS HEAD 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. +.\" +.\" $FreeBSD$ +.\" +.Dd August 3, 2009 +.Dt HRL 8 +.Os +.Sh NAME +.Nm hrl +.Nd display and update Hierarchical Resource Limits database +.Sh SYNOPSIS +.Nm +.Op Ar filter +.Nm +.Fl a +.Op Ar rule +.Nm +.Fl l +.Op Ar filter +.Nm +.Fl r +.Op Ar filter +.Nm +.Fl u +.Op Ar filter +.Sh DESCRIPTION +When called without options, the +.Nm +command writes currently defined HRL rules to standard output. +.Pp +If a +.Ar filter +argument is specified, only rules matching the filter are displayed. +The options are as follows: +.Bl -tag -width indent +.It Fl a Ar rule +Add +.Ar rule +to the HRL database. +.It Fl l Ar filter +Display rules applicable to the process defined by +.Ar filter . +.It Fl r Ar filter +Remove rules matching +.Ar filter +from the HRL database. +.It Fl l Ar filter +Display resource usage for a subject (process, user, login class +or jail) matching the +.Ar filter . +.Pp +.Sh RULE SYNTAX +Syntax for a rule is subject:subject-id:resource:action=amount/per. +.Pp +Subject defines the kind of entity the rule applies to. +It can be either process, user, login class, or jail. +.Pp +Subject ID identifies the subject. It can be user name, +login class name, or a numerical UID, or JID. +.Pp +Resource identifies the resource the rule controls. +.Pp +Action defines what will happen when a process exceeds the allowed amount. +It can be either deny, delay, log, sighup, sigint, sigkill, sigsegv, sigxcpu, +or sigxfsz. +.Pp +Amount defines how much of the resource a process can use before +the defined action triggers. +.Pp +The per field defines what entity the limit gets accounted for. +For example, rule "loginclass:users:memoryuse:deny=100M/process" means +that each process of any user belonging to login class "users" may use up to 100MB +of memory. +Rule "loginclass:users:memoryuse:deny=100M/user" would mean that the sum of +memory used by all processes of any user belonging to the login class "users" +will not exceed 100MB. +Rule "loginclass:users:memoryuse:deny=100M/loginclass" would mean that the sum of +memory used by all processes of all users belonging to that login class will +not exceed 100MB. +.Pp +Valid rule has all of these fields specified, except for the per, which defaults +to the value of subject. +.Pp +A filter is a rule for which one of more fields other than per is left empty. +For example, a filter that matches every rule could be written as ":::=/", +or, in short, ":". A filter that matches all the login classes would be +"loginclass:". A filter that matches all defined limits for maxprocesses +resource would be "::maxprocesses". +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr id 1 , +.Xr limits 1 +.Sh HISTORY +The +.Nm +command appeared in +.Fx 9.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +command was written by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org . diff -urNp current/usr.sbin/hrl/hrl.c hrl/usr.sbin/hrl/hrl.c --- current/usr.sbin/hrl/hrl.c 1970-01-01 01:00:00.000000000 +0100 +++ hrl/usr.sbin/hrl/hrl.c 2009-12-18 11:30:00.163127609 +0100 @@ -0,0 +1,356 @@ +/*- + * Copyright (c) 2009 Edward Tomasz Napierała + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HRL_DEFAULT_BUFSIZE 4096 + +static id_t +parse_user(const char *s) +{ + id_t id; + char *end; + struct passwd *pwd; + + pwd = getpwnam(s); + if (pwd != NULL) + return (pwd->pw_uid); + + if (!isnumber(s[0])) + errx(1, "uknown user '%s'", s); + + id = strtod(s, &end); + if ((size_t)(end - s) != strlen(s)) + errx(1, "trailing characters after numerical id"); + + return (id); +} + +static id_t +parse_group(const char *s) +{ + id_t id; + char *end; + struct group *grp; + + grp = getgrnam(s); + if (grp != NULL) + return (grp->gr_gid); + + if (!isnumber(s[0])) + errx(1, "uknown group '%s'", s); + + id = strtod(s, &end); + if ((size_t)(end - s) != strlen(s)) + errx(1, "trailing characters after numerical id"); + + return (id); +} + +/* + * This routine replaces user/group name with numeric id. + */ +static char * +resolve_ids(char *rule) +{ + id_t id; + const char *subject, *textid, *rest; + char *resolved; + + subject = strsep(&rule, ":"); + textid = strsep(&rule, ":"); + if (textid == NULL) + errx(1, "error in rule specification -- no subject"); + if (rule != NULL) + rest = rule; + else + rest = ""; + + if (strcasecmp(subject, "u") == 0) + subject = "user"; + else if (strcasecmp(subject, "g") == 0) + subject = "group"; + else if (strcasecmp(subject, "p") == 0) + subject = "process"; + else if (strcasecmp(subject, "l") == 0 || + strcasecmp(subject, "c") == 0 || + strcasecmp(subject, "class") == 0) + subject = "loginclass"; + + if (strcasecmp(subject, "user") == 0 && strlen(textid) > 0) { + id = parse_user(textid); + asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); + } else if (strcasecmp(subject, "group") == 0 && strlen(textid) > 0) { + id = parse_group(textid); + asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); + } else + asprintf(&resolved, "%s:%s:%s", subject, textid, rest); + + if (resolved == NULL) + err(1, "asprintf"); + + return (resolved); +} + +/* + * Print rules, one per line, */ +static void +print_rules(char *rules) +{ + char *rule; + const char *subject, *textid, *rest; + id_t id; + struct passwd *pwd; + struct group *grp; + + while ((rule = strsep(&rules, ",")) != NULL) { + if (rule[0] == '\0') + break; /* XXX */ + subject = strsep(&rule, ":"); + textid = strsep(&rule, ":"); + if (textid == NULL) + errx(1, "rule passed from the kernel didn't contain subject"); + if (rule != NULL) + rest = rule; + else + rest = ""; + + /* Replace numerical user and group ids with names. */ + if (strcasecmp(subject, "user") == 0) { + id = parse_user(textid); + pwd = getpwuid(id); + if (pwd != NULL) + textid = pwd->pw_name; + } else if (strcasecmp(subject, "group") == 0) { + id = parse_group(textid); + grp = getgrgid(id); + if (grp != NULL) + textid = grp->gr_name; + } + + printf("%s:%s:%s\n", subject, textid, rest); + } +} + +static void +add_rule(char *rule) +{ + int error; + + error = hrl_add_rule(rule, strlen(rule) + 1, NULL, 0); + if (error) + err(1, "hrl_add_rule"); + free(rule); +} + +static void +show_limits(char *filter) +{ + int error; + char *outbuf = NULL; + size_t outbuflen = HRL_DEFAULT_BUFSIZE / 4; + + do { + outbuflen *= 4; + outbuf = realloc(outbuf, outbuflen); + if (outbuf == NULL) + err(1, "realloc"); + + error = hrl_get_limits(filter, strlen(filter) + 1, outbuf, outbuflen); + if (error && errno != ERANGE) + err(1, "hrl_get_limits"); + } while (error && errno == ERANGE); + + print_rules(outbuf); + free(filter); + free(outbuf); +} + +static void +remove_rule(char *filter) +{ + int error; + + error = hrl_remove_rule(filter, strlen(filter) + 1, NULL, 0); + if (error) + err(1, "hrl_remove_rule"); + free(filter); +} + +/* + * Query the kernel about a resource usage and print it out. + */ +static void +show_usage(char *filter) +{ + int error; + char *outbuf = NULL, *tmp; + size_t outbuflen = HRL_DEFAULT_BUFSIZE / 4; + + do { + outbuflen *= 4; + outbuf = realloc(outbuf, outbuflen); + if (outbuf == NULL) + err(1, "realloc"); + + error = hrl_get_usage(filter, strlen(filter) + 1, outbuf, outbuflen); + if (error && errno != ERANGE) + err(1, "hrl_get_usage"); + } while (error && errno == ERANGE); + + for (tmp = outbuf; *tmp != '\0'; tmp++) + if (*tmp == ',') + *tmp = '\n'; + + printf("%s\n", outbuf); + free(filter); + free(outbuf); +} + +/* + * Query the kernel about resource limit rules and print them out. + */ +static void +show_rules(char *filter) +{ + int error; + char *outbuf = NULL; + size_t filterlen, outbuflen = HRL_DEFAULT_BUFSIZE / 4; + + if (filter != NULL) + filterlen = strlen(filter) + 1; + else + filterlen = 0; + + do { + outbuflen *= 4; + outbuf = realloc(outbuf, outbuflen); + if (outbuf == NULL) + err(1, "realloc"); + + error = hrl_get_rules(filter, filterlen, outbuf, outbuflen); + if (error && errno != ERANGE) + err(1, "hrl_get_rules"); + } while (error && errno == ERANGE); + + print_rules(outbuf); + free(outbuf); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: hrl [-a rule | -l filter | -r filter | -u filter | filter]\n"); + exit(1); +} + +int +main(int argc __unused, char **argv __unused) +{ + int ch, aflag = 0, lflag = 0, rflag = 0, uflag = 0; + char *rule = NULL; + + while ((ch = getopt(argc, argv, "a:l:r:u:")) != -1) { + switch (ch) { + case 'a': + aflag = 1; + rule = strdup(optarg); + break; + case 'l': + lflag = 1; + rule = strdup(optarg); + break; + case 'r': + rflag = 1; + rule = strdup(optarg); + break; + case 'u': + uflag = 1; + rule = strdup(optarg); + break; + + case '?': + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc > 1) + usage(); + + if (rule == NULL) { + if ( argc == 1) + rule = strdup(argv[0]); + else + rule = strdup("::"); + } + + if (aflag + lflag + rflag + uflag + argc > 1) + errx(1, "only one flag or argument may be specified " + "at the same time"); + + rule = resolve_ids(rule); + + if (aflag) { + add_rule(rule); + return (0); + } + + if (lflag) { + show_limits(rule); + return (0); + } + + if (rflag) { + remove_rule(rule); + return (0); + } + + if (uflag) { + show_usage(rule); + return (0); + } + + show_rules(rule); + return (0); +}