Pseudo-devices On GNU/Linux
先分享一则有意思Q&A,来自The FreeBSD Funnies 17.4 。
Where does data written to* /dev/null* go?
It goes into a special data sink in the CPU where it is converted to heat which is vented through the heatsink / fan assembly. This is why CPU cooling is increasingly important; as people get used to faster processors, they become careless with their data and more and more of it ends up in /dev/null, overheating their CPUs. If you delete /dev/null (which effectively disables the CPU data sink) your CPU may run cooler but your system will quickly become constipated with all that excess data and start to behave erratically. If you have a fast network connection you can cool down your CPU by reading data out of /dev/random and sending it off somewhere; however you run the risk of overheating your network connection and / or angering your ISP, as most of the data will end up getting converted to heat by their equipment, but they generally have good cooling, so if you do not overdo it you should be OK.
写入* /dev/null* 的数据去了哪里?
它们进入了CPU的“垃圾槽”中并被转化为热量最终由散热装置吹走了 ;)这也是为什么CPU的散热越来越重要;随着人们习惯于使用那些飞快的CPU,他们也对那些被送入 /dev/null 的数据不那么关心了——于是CPU可倒霉了。如果你删掉了/dev/null(这会马上堵住CPU的“垃圾槽”。真的可以删掉。。。),你的CPU可能会舒服点但是你的系统也会因为那些无处可去的额外数据而马上”便秘“。如果你的网挺快,也可以试试从dev/random读取数据并发到网上(后面会讲解random是如何工作的),以此来为你的CPU降温。然而这也是有风险的,毕竟网络和你的磁盘也会发热(I/O桥??),电信运营商也会抓狂——那些数据会被他们的设备转化为热量。不过他们的散热一般很好,所以偶尔搞一搞也是无妨的 ; )
下面开始正经的说一说/dev下的Pseudo-devices:
/dev/random与/dev/urandom
首先介绍一下随机数的性质分类(学过密码学的跳过吧)
- 随机性——不存在统计学的偏差,是完全杂乱的数列(前几天看家伟做的一道线性同余的题就是这种)。
- 不可预测性——不能从过去的数列推测出下一个出现的数。
- 不可重现性——除非将数列记录下来,否则不可能重现相同的数列。
以上三个性质中,越往下越严格。密码技术中所使用的随机数,仅仅具备随机性是不够的,至少还要具备不可预测的性质。 一般的,将以上三个性质分别称为”弱伪随机数“,”强伪随机数“,”真随机数“。
给真随机数举一个简单的例子:你不小心把一个玻璃杯打碎了,这个玻璃杯打碎过程中的破碎轨迹,破碎后碎片的分布对所有人来说都是随机的,也是当前几乎无法完全重现的。真随机数生成麻烦,一般用来做伪随机数的种子,以此来利用扩张的随机性。
为了方便计算机获得真随机数(不用你去砸杯子了),新型的X86 CPU中都内置了数字随机数生成器(Digital Random Number Generator, DRNG),并提供了RDSEED和RDRAND两条指令。这种CPU生成随机数的原料(随机信号源)来自于电路中产生的热噪声。(如果你用过GnuPG生成对称密钥,它会提醒你移动鼠标或者使用磁盘,这也是为了方便收集真随机数)。
从Linux 1.3.30开始,/dev有了random和urandom两个character special files,通过这两个接口,用户可以使用内核的随机数字发生器。
随机数字发生器会收集硬件驱动产生的背景噪声(真随机数),并把它们放在随机池里。同时发生器会时时记录下随机池里剩余的随机数。通过这个随机池,我们就可以产生很多伪随机数了。
那这两个文件有什么区别呢?
当random文件/设备被读时,它仅仅只会返回随机池里的真随机数,所以random文件适合于那些需要非常高安全性的场合——比如需要使用一次性密码本或者作为其他伪随机数生成器的种子(比如密钥生成器)。当随机池空的时候,random会暂停输出知到新的热噪声被收集到池子中。(你可以试一下cat /dev/random,待到输出停止后再移动一下鼠标或者打开一个视频,新的真随机数就会继续被输出。)
而当你读urandom文件/设备时,它不会为了等待新的噪声而暂停输出。如果池子里的真随机数不够,它会调用内核里的一个伪随机发生器来继续产生随机数。所以,在这种情况下攻击是可能的,但是到目前为止没有发现针对这个文件/设备的有效攻击。不放心的话就用random吧。
从3.16的内核开始,从urandom读的话最多能收到32MB的随机数,从random的话有512bytes(2.6以前只有340bytes)。
如果你向random或者urandom写数据的话,写入的数据会覆盖掉随机池里的真随机数,但是这不会提高安全性;也不会提高从random读出随机数的速度。
提醒一下,很多伪随机数的库函数都仅仅满足了随机性,例如C的rand,java的java.util.Random等等,以后写安全方面的代码时要注意。 (上次一航讲的一个web500就用到了弱伪随机数的可预测性)
/dev/full
这个文件通常被用来测试软件是如何处理磁盘空间不够的情况,当你向这个文件写数据时,会收到一个ENOSPC error。从该文件读的话会得到一连串NULL('\00')字符。
frank@under:~$ echo hello > /dev/full
bash: echo: write error: No space left on device
/dev/null
”talk is cheap, show me the code“,就按着kernel的源码来讲吧。
声明:
static const struct file_operations null_fops = {
.llseek = null_lseek,
.read = read_null,
.write = write_null,
.read_iter = read_iter_null,
.write_iter = write_iter_null,
.splice_write = splice_write_null,
};
只分析read_null和write_null,其他的类似。
read_null():
static ssize_t read_null(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
return 0;
}
可以看到,如果从null里面读取数据的话就是返回有符号整型”0“,也不会处理用户给他的file指针或者buffer指针和其他数据。,通常情况下这就是一个EOF——你得不到任何数据。
write_null():
static ssize_t write_null(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
return count;
}
这个也是”毫无用处“,仅仅只是返回我们告诉null我们想要写入文件的字节大小(也是有符号整型)。通过这种通用的”写入成功“返回方式,使得一般的软件将流定向到null可以收到”正确写入“的消息,猜想这会方便软件无用流的编写。
/dev/zero
上面说到读null会得到一个有符号整型“0”。那么zero呢?你会得到一连串的NULL字符('\0')(说明处理了文件指针)。
$ dd if=/dev/null of=file count=10
0+0 records in
0+0 records out
0 bytes (0 B) copied, 0.000276193 s, 0.0 kB/s
$ dd if=/dev/zero of=file count=10
10+0 records in
10+0 records out
5120 bytes (5.1 kB) copied, 0.00090775 s, 5.6 MB/s
声明:
static const struct file_operations zero_fops = {
.llseek = zero_lseek,
.write = write_zero,
.read_iter = read_iter_zero,
.write_iter = write_iter_zero,
.mmap = mmap_zero,
.get_unmapped_area = get_unmapped_area_zero,
#ifndef CONFIG_MMU
.mmap_capabilities = zero_mmap_capabilities,
#endif
};
仅分析一下zero_lseek和write_zero。
zero_lseek:
#define zero_lseek null_lseek
/*
* Special lseek() function for /dev/null and /dev/zero. Most notably, you
* can fopen() both devices with "a" now. This was previously impossible.
* -- SRB.
*/
static loff_t null_lseek(struct file *file, loff_t offset, int orig)
{
return file->f_pos = 0;
}
f_pos定义在file结构体(定义在<linux/fs.h>),表示文件当前的读写位置,因此现在可以appending的方式打开这两个特殊文件了。
write_zero:
#define write_zero write_null
static ssize_t write_null(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
return count;
}
这就很有意思了,看起来向zero写文件和null应该是一个效果。。。(不过这么写代码的话别人会觉得奇怪吧)
看来null和zero很像两兄弟,不过传统上还是建议把null当成一个”只写“的黑洞,把zero当成一个只读的NULL字符(‘\0’)产生器吧。
ps:
- 大家可能会对null返回的整型“0“和zero返回的NULL字符 '\0'的区别感到疑惑,可以参考stackoverflow上一篇文章。
- /dev/zero 通常被用来创建swap。
- 详细的文档可以man full、urandom、zero等。
happy hacking!
参考:
- https://en.wikipedia.org/wiki//dev/random
- https://en.wikipedia.org/wiki/Null_device
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/char/mem.c?id=refs/tags/v4.9-rc3#n774
- https://stackoverflow.com/questions/3690273/did-i-understand-dev-urandom
- https://www.reddit.com/r/linuxquestions/comments/5b5wq5/eli5_how_does_devnull_work/
- https://unix.stackexchange.com/questions/63238/purpose-of-dev-zero
- https://unix.stackexchange.com/questions/254384/difference-between-dev-null-and-dev-zero
- 《图解密码学》第三版
- Linux Programmer's Manual