Search code examples
crubycpu-usagememory-limitleast-privilege

How to limit program runtime, memory usage and as a specific user programmatically in Linux


Is there a programmatically way to limit duration, memory usage and run as less privileged user of a Linux program execution in C/C++ or Ruby ?

since system or `` can't do this.

sprintf(cmd_str,"/tmp/pro-%d < /tmp/in.txt > /tmp-%d.txt",id,id);
system(cmd_str); // in C

`/tmp/pro-#{id} < /tmp/in.txt > /tmp/out-#{id}.txt` // in Ruby

both statement makes that command run as the same user as the executor, uses whole processing power and memory as they like.


Solution

  • You'll want to use the setrlimit syscall to limit memory (Process::RLIMIT_AS). To limit the runtime of the program, you can only control the total number of seconds a process gets CPU time (so that doesn't account for time spent sleeping or waiting on I/O). That's done with Process::CPU.

    Drop privileges with Process::Sys.setgid followed by Process::Sys.setuid after setting these rlimits, but before calling your target process with Process::exec.

    Example target program:

    #include <stdio.h>
    #include <stdlib.h>
    
    #include <unistd.h>
    
    #define ALLOC_SIZE_1  1024
    #define ALLOC_SIZE_2  (1024 * 1024 * 5)
    
    int
    main(int argc, char *argv[])
    {
      char *buf;
    
      fprintf(stderr, "[+] uid: %d, gid: %d\n", getuid(), getgid());
    
      fprintf(stderr, "[+] trying to allocate %d bytes (should succeed)...\n", ALLOC_SIZE_1);
      if (NULL == (buf = malloc(ALLOC_SIZE_1))) {
        fprintf(stderr, "[!] failed!\n");
        return -1;
      }
    
      fprintf(stderr, "[+] success.\n");
      free(buf);
    
      fprintf(stderr, "[+] trying to allocate %d bytes (should fail)...\n", ALLOC_SIZE_2);
      if (NULL != (buf = malloc(ALLOC_SIZE_2))) {
        fprintf(stderr, "[!] succeeded!  (should have failed.)\n");
        return -1;
      }
    
      fprintf(stderr, "[+] ok.  now doing infinite loop (should get killed pretty soon)...\n");
      for (;;);
    
      return 0;
    }
    

    And accompanying Ruby script to invoke it (run this script as root with, e.g. sudo /tmp/foo.rb):

    #!/usr/bin/env ruby
    
    TARGET_GID = 99
    TARGET_UID = 99
    
    Process::setrlimit(Process::RLIMIT_AS, 1024 * 1024 * 5)
    Process::setrlimit(Process::RLIMIT_CPU, 3)
    
    Process::Sys.setgid(TARGET_GID)
    Process::Sys.setuid(TARGET_UID)
    
    Process::exec('/tmp/test')
    

    And finally, output of running on my machine:

    $ sudo ./run.rb
    [+] uid: 99, gid: 99
    [+] trying to allocate 1024 bytes (should succeed)...
    [+] success.
    [+] trying to allocate 5242880 bytes (should fail)...
    [+] ok.  now doing infinite loop (should get killed pretty soon)...
    $