14

While working on a project that reads from /dev/urandom to generate random bytes, it was suggested that I check to make sure that /dev/urandom is a device not just a file.

The most straightforward way seems to be something like:

/**
 * Is the given file a device?
 * 
 * @param string|resource $file
 * @return boolean
 */
function is_device($file)
{
    if (is_resource($file)) {
        $stat = fstat($file);            
    } elseif (is_readable($file) && !is_link($file)) {
        $stat = stat($file);
    } else {
        return false;
    }
    return $stat['rdev'] !== 0;
}

My question is two-fold:

  1. Is this the best way to check that this file is a device?
  2. Are there circumstances where this $stat['rdev'] !== 0 check can fail?

Important: The solution I need must be in PHP without depending on any PECL extensions or custom C code. The project is a pure PHP 5 polyfill of PHP 7's random_bytes() and random_int() functions and is intended to be installable in anyone else's PHP 5 projects by Composer.

Scott Arciszewski
  • 33,610
  • 16
  • 89
  • 206
  • [Have you seen this?](http://insanecoding.blogspot.com/2014/05/a-good-idea-with-bad-usage-devurandom.html) –  Jul 27 '15 at 04:30
  • My current unofficial stance is, "TOCTOU issues and file descriptor exhaustion attacks are out of scope. If your filesystem is pwned, there's nothing your PHP web app can do to save you." – Scott Arciszewski Jul 27 '15 at 04:39

2 Answers2

4

well, you can use filetype().

if you do a fast ll on urandom, you will see:

ll /dev/urandom 
crw-rw-rw- 1 root root 1, 9 Jul 26 17:38 /dev/urandom

that 'c' at the beginnng means it's a "character" filetype. you can check out all the different filetypes here:

https://en.wikipedia.org/wiki/Unix_file_types

this means that if you run

filetype("/dev/urandom");

you will get "char" back, meaning character filetype. that should do the trick.

frymaster
  • 664
  • 7
  • 12
  • 1
    "you should take the time to investigate openssl_random_pseudo_bytes()." [That is not the first choice I would recommend to anyone](https://github.com/paragonie/random_compat/blob/master/ERRATA.md). The project in question is a polyfill for PHP 7's `random_bytes()` and `random_int()` functions. – Scott Arciszewski Jul 27 '15 at 04:16
2

Update

My original solution turned out to be just a re-implementation of filetype($filepath) === 'char', so filetype() seems to be the only thing you need.


Based on @frymaster's answer ...

I looked at how PHP's stat() function works, looking for "char" and found this.

Combined with the stat(2) manual for both Linux and FreeBSD, as well as a comment on PHP's manual for stat(), I came up with the following:

function is_device($filepath)
{
        if ( ! file_exists($filepath) OR (stripos(PHP_OS, 'Linux') === false && stripos(PHP_OS, 'BSD') === false))
        {
                return false;
        }

        $mode = stat($filepath)['mode'];
        return (020000 === ($mode & 0170000));
}

Works on my Linux system.

Update (to answer the second question)

Yes, stat($file)['rdev'] !== 0 can fail. From what I found, it may return -1 if not supported by the OS, while even a positive value may point at a different device type. Its values also appear to be OS-dependent.

Narf
  • 14,600
  • 3
  • 37
  • 66
  • It appears that libsodum does something very similar: https://github.com/jedisct1/libsodium/blob/024b74c84995e68c804868b1189d75f571794ad7/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c#L118 – Scott Arciszewski Jul 30 '15 at 18:58
  • 2
    And that turned out to be what `filetype()` does to return 'char'. :) Just updated the answer ... which is now very much the same as what @frymaster said. – Narf Jul 30 '15 at 19:35
  • 1
    You're welcome. :) And I did too - I didn't know any of this before and in fact didn't know PHP had a `filetype()` function and thought you were talking about something else. Yet, took another clue from your answer and made it a full circle - collective thinking at work! :D – Narf Jul 30 '15 at 22:32