Making System Calls From Ruby


While I was reading about Linux system calls, I was curious about how to perform them in Ruby specially that Ruby isn’t considered a “system” programming language. However, It’s very possible to perform system calls using Ruby. In this post, I’m going to demonstrate the different ways to make system calls using Ruby. But first, let us define what system calls are. Simply put, a system call is a way to enter the system kernel, execute an operation, and then return from the kernel.

In the following sections, I’ll demonstrate three [almost] different ways to perform system calls from Ruby. All the code is tested on a 64-bit GNU/Linux system. Although there are some notes about differences in other operating systems, if you tested something on your operating system and want me to add it here, please add a comment or send me a message.

1. Kernel.syscall

This first, and most straightforward way to perform a system call is by using Kernel.syscall. In fact, internally, Kernel.syscall calls the C function syscall directly. This is how it’s done:

syscall syscall_number, syscall_arguments...

syscall_number is a unique number for each system operation and can be obtained for 64-bit Linux systems from this table.

So, for example, to perform an exit system call with exit status 1, call the following Ruby program:

syscall 60, 1

and check the exit code in your shell:

echo $? # => 1

Note: I’m using Ruby 2.6.0 without the --jit option to run this example. For Ruby versions prior to 2.6.0 on Linux, you might want to use the exit_group system call (with syscall_number 231) instead of exit to terminate all the threads the Ruby process is using otherwise, the Ruby process will freeze waiting to terminate the remaining thread(s).

However, there’s a caveat here. The Ruby documentation says that Kernel.syscall isn’t safe nor it is portable. If you ran the last program using the Ruby -v command line option (for verbose), you’ll see the following message:

warning: We plan to remove a syscall function at future release. DL(Fiddle) provides safer alternative.

Thus, Fiddle will be our next alternative for making system calls in Ruby.

2. Fiddle

Fiddle is a libffi wrapper written for Ruby. Luckily, it is already a part of Ruby’s standard library. libffi is a foreign function interface (FFI) implementation that provides a C interface for calling compiled code (functions, really) from your program at runtime.

The following program demonstrates how to use Fiddle to call a C function from Ruby:

require 'fiddle'

libc     = Fiddle.dlopen('/lib/libc.so.6')
function = Fiddle::Function.new(libc['syscall'], [Fiddle::TYPE_INT,
                                                 Fiddle::TYPE_INT], Fiddle::TYPE_INT)

function.call(60, 1)

The code calls the C function syscall to perform the exit system call. In addition to the name of the function, you have to pass in an array that contains the types of each argument the syscall/function takes, and another argument for the function return type, which is an integer number in this case. For example, the exit syscall function takes two parameters; one for the exit syscall itself, the number 60, and another for the exit status, which is 1 here. Thus, we have an array of two integers.

Note: the path to libc specified in the code will differ for other operating systems. For MacOS for example, it should be set to /usr/lib/libc.dylib. Please also note that I didn’t test it myself so it might be a different value.

Check the exit code:

echo $? # => 1

3. FFI

Ruby-ffi is an FFI implementation in Ruby. Other than making system calls, FFI makes it possible to fully write C extensions without the need to write pure C code. By far, this is the safest, most convenient method as it automatically locates the path to the libc library without the need to specify it manually as in the case with Fiddle above.

The code that makes a system call looks like this:

require 'ffi'

module SyscallRunner
  extend FFI::Library
  ffi_lib FFI::Library::LIBC
  attach_function :syscall, [:int, :int], :int
end

SyscallRunner.syscall 60, 1

Again, you need to pass in an array that contains the types of each argument the function takes, and another argument for the function return type.

And again, check the exit status:

echo $? # => 1

Conclusion

Although Ruby might not be considered a system programming language, Ruby is capable of going low level and performing system calls in more than one way. All the methods delegate the system call to C syscall function in a way or another. The most secure, most scalable method is using Ruby-ffi to invoke C syscall function.