15

I want to call a Linux syscall (or at least the libc wrapper) directly from a scripting language. I don't care what scripting language - it's just important that it not be compiled (the reason basically has to do with not wanting a compiler in the dependency path, but that's neither here nor there). Are there any scripting languages (shell, Python, Ruby, etc) that allow this?

In particular, it's the getrandom syscall.

joshlf
  • 345
  • 3
  • 17
  • 3
    getrandom merely pulls in random bytes from `/dev/urandom`. You can certainly do that from a shell script. – steve Mar 24 '17 at 21:00
  • @steve indeed, unless of course `/dev` isn't yet available. But then hard to imagine Perl would be! – derobert Mar 24 '17 at 21:26
  • Critically, I want this to block until the entropy pool is initialized, which reading from /dev/urandom as a file doesn't do. – joshlf Mar 24 '17 at 23:49
  • 5
    Read from `/dev/random` until it unblocks, then read from `/dev/urandom`? – bishop Mar 25 '17 at 01:13
  • So I wasn't previously aware that `/dev/random` blocked in this way; somebody just told me. So that works, thanks! – joshlf Mar 25 '17 at 01:19
  • 1
    "the reason basically has to do with not wanting a compiler in the dependency path, but that's neither here nor there" -> Huh? If you mean the *runtime* dependency path, then you won't in any case. You do not need a C compiler to run a binary that was compiled from C. If you mean that you do not want to depend on the ability to compile things for your target architecture, because you think you will not have that ability, then it is unlikely you will be able to make Python, Bash, or any other real scripting language run on that platform either. – Kevin Mar 26 '17 at 01:13
  • It's neither - it just has to do with how we have our build process set up; it's easier not to have to compile things in our build process. We certainly could, but a script is easier. – joshlf Mar 26 '17 at 07:30

3 Answers3

33

Perl allows this with its syscall function:

$ perldoc -f syscall
    syscall NUMBER, LIST
            Calls the system call specified as the first element of the list,
            passing the remaining elements as arguments to the system call. If
⋮

The documentation also gives an example of calling write(2):

require 'syscall.ph';        # may need to run h2ph
my $s = "hi there\n";
syscall(SYS_write(), fileno(STDOUT), $s, length $s);

Can't say I've ever used this feature, though. Well, before just now to confirm the example does indeed work.

This appears to work with getrandom:

$ perl -E 'require "syscall.ph"; $v = " "x8; syscall(SYS_getrandom(), $v, length $v, 0); print $v' | xxd
00000000: 5790 8a6d 714f 8dbe                      W..mqO..

And if you don't have getrandom in your syscall.ph, then you could use the number instead. It's 318 on my Debian testing (amd64) box. Beware that Linux syscall numbers are architecture-specific.

derobert
  • 107,579
  • 20
  • 231
  • 279
28

In Python you can use the ctypes module to access arbitrary functions in dynamic libraries, including syscall() from libc:

import ctypes

SYS_getrandom = 318 # You need to check the syscall number for your target architecture

libc = ctypes.CDLL(None)
_getrandom_syscall = libc.syscall
_getrandom_syscall.restypes = ctypes.c_int
_getrandom_syscall.argtypes = ctypes.c_int, ctypes.POINTER(ctypes.c_char), ctypes.c_size_t, ctypes.c_uint

def getrandom(size, flags=0):
    buf = (ctypes.c_char * size)()
    result = _getrandom_syscall(SYS_getrandom, buf, size, flags)
    if result < 0:
        raise OSError(ctypes.get_errno(), 'getrandom() failed')
    return bytes(buf)

If your libc includes the getrandom() wrapper function, you can call it too:

import ctypes

libc = ctypes.CDLL(None)
_getrandom = libc.getrandom
_getrandom.restypes = ctypes.c_int
_getrandom.argtypes = ctypes.POINTER(ctypes.c_char), ctypes.c_size_t, ctypes.c_uint

def getrandom(size, flags=0):
    buf = (ctypes.c_char * size)()
    result = _getrandom(buf, size, flags)
    if result < 0:
        raise OSError(ctypes.get_errno(), 'getrandom() failed')
    return bytes(buf)
cg909
  • 6,908
  • 1
  • 27
  • 30
  • Any chance you could add an example of calling the libc `getrandom` function directly rather than the syscall `getrandom`? Is that possible? – joshlf Mar 28 '17 at 02:52
  • @joshlf Of course it's possible. I edited my answer. – cg909 Mar 28 '17 at 07:55
  • Do you know if there's a way to dynamically look up the correct value of the `SYS_getrandom` value at runtime (so you get it right for the current platform)? E.g., by parsing `/usr/include` header files? – joshlf Oct 13 '17 at 20:03
  • I didn't try it, but you might have luck with [pycparser](https://github.com/eliben/pycparser). – cg909 Oct 31 '17 at 19:22
17

Ruby has a syscall(num [, args...]) → integer function.

For example:

irb(main):010:0> syscall 1, 1, "hello\n", 6
hello
=> 6

With getrandom():

irb(main):001:0> a = "aaaaaaaa"
=> "aaaaaaaa"
irb(main):002:0> syscall 318,a,8,0
=> 8
irb(main):003:0> a
=> "\x9Cq\xBE\xD6|\x87\u0016\xC6"
irb(main):004:0> 
lgeorget
  • 13,656
  • 2
  • 41
  • 63