• Limiting per User Memory with Cgroup

    Sat, 24 Jan 2015 11:42:06 +0100Posted by André Landwehr

    [Tips & Tricks]

    I teach my test engineers programming in C and recently introduced malloc. Since they don't have Linux machines themselves I gave them logins on my Desktop where they can do the exercises. A few days ago, one of them called malloc in a loop, bringing up the OOM killer and effectively killing my xserver on the way. Call it collateral damage, but it was about time to limit other user's memory...
    Ulimit comes to mind first of course, but ulimit only limits the amount of memory per process, which is not really what I wanted to do. There is a relatively new mechanism called cgroup, which looked very promising. Cgroup is mainly a daemon in kernel space that exports subsystems as filesystems, which are typically mounted inside sysfs. Everything can be configured then using standard command line tools, but is more easily done with the cgconfigparser program.
    On my Debian sid system, I had to the following modifications:

    • Debian by default does not export the memory subsystem and does not account swap space usage, so I had to adjust the kernel command line. In /etc/default/grub, the corresponding line is now GRUB_CMDLINE_LINUX_DEFAULT="quiet cgroup_enable=memory swapaccount=1".
    • In /etc/cgconfig.conf instruct the cgconfigparser to mount and configure the subsystems we need. Mind that the "cpuset" configuration is mandatory when using cgroup, you cannot leave it out:
      mount { cpu = /sys/fs/cgroup; cpuset = /sys/fs/cgroup; memory = /sys/fs/cgroup; } group memlimit { memory { memory_limit_in_bytes = 1024M; memory.memsw.limit_in_bytes = 1024M; } cpuset { cpuset.mems = 0; cpuset.cpus = 0-3; } }
    • In /etc/rc.local I added the call to the cgconfigparser, which executes what I configured in cgconfig.conf. Just add cgconfigparser -f /etc/cgconfig.conf
    • Now, to make a particular user belong to a cgroup, setup the file /etc/cgrules.conf:
      myuser memory /memlimit
      Add one line of this style for each user.
    • Then setup the cgroup pam module, wich reads the cgrules.conf. Adding the following line at the end of /etc/pam.d/common-session did the trick for me:
      session optional pam_cgroup.so
    ATTENTION: libcgroup 0.41, which is also used in Debian sid (and as of now probably everywhere with the exception of Redhat Enterprise Server) contains a bug which makes the pam module do nothing at all. A colleague of mine reported the problem in upstream, so hopefully this gets fixed soon. For now, get the libcgroup source and open libcgroup-0.41/src/pam/pam_cgroup.c. In the function pam_sm_open_session() fix the call to cgroup_change_group_uid_gid_flags() by replacing "CGFLAG_USECACHE" with 0.
    Well, that's all. Reboot (if necessary for new kernel command line to take effect) and login as a restricted user. Call ps -o cgroup $$ and you should see that your shell is restricted. When the total amount of memory used by the user exceeds one gig, the allocating process is killed.