Making System Calls From Ruby Systems
December 26, 2018While 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 theexit_group
system call (with syscall_number 231) instead ofexit
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.