1文件IO

一、open和openat

open是正常的打开文件

而POSIX.1新增了openat,希望解决两个问题。

  1. 让线程可以使用相对路径打开目录中的文件
  2. 避免time-of-check-to-time-of-use(TOCTTOU)

1.什么是TOCTTOU错误?

第二个调用依赖第一个的调用结果,那么程序就是脆弱的。如果两个调用并不是原子操作,在这两个函数之间文件可能发生改变了,这样也就造成了第一个调用的结果不再有效。通过这种方式可能骗取特权程序的权限。

例:

if(access("file",W_OK)!= 0) { exit(1); } //此时是下部分的调用结果依赖于上部分的结果,但是在判断完文件有效到打开文件的过程中,文件可能被篡改 fd = open("file",O_WRONLY); //可能此时已经被攻击 write(fd,buffer,sizeof(buffer));

攻击代码

//在open之前access之后使用 symlink("/etc/passwd","file"); //此时能骗取特权程序的权限,使得它对passwd进行操作,那可能造成无法登录

  • 这如何提高安全性?

首先,请注意,文件描述符是目录的文件描述符。在打开目录(用于读取)时,它存在,并且进程有权访问该目录及其中的文件。此外,由于此进程打开了目录,因此在进程关闭目录文件描述符之前,对该目录的最后引用不会消失。如果它位于已挂载的文件系统上,则在程序终止之前无法卸载该文件系统(因为进程已打开目录)。如果该目录被移动(在同一文件系统上),则将继续相对于该目录在文件系统中的当前位置创建文件。

从现在开始,事情变得更加投机——我还没有正式测试这些观察结果。

即使删除了该目录,您似乎仍然可以创建相对于它的文件。如果名称是简单名称(或 ),则应该没问题。如果名称具有更多的路径 (),则在删除目录时创建或打开文件将失败,因为所有子目录也将被删除。"new_file""./new_file""subdir/new_file"

当然,有 mkdirat() 来创建子目录。

据推测,文件系统必须在所有这些之后进行清理,这可能非常复杂。这意味着实际上您可能无法在具有打开的文件描述符但已删除名称的目录中创建文件。但是,您会知道这不再可能,而不是假设它是同一个目录。

无论哪种方式,只要您在使用正确的函数时一直小心且一致,攻击者就很难混淆您的程序以在错误的目录中创建文件。*at()

删除了一组TOCTOU攻击;那些依赖于重命名(或可能删除)的目录并使用新名称(例如指向其他位置的符号链接)的攻击将被挫败,因为文件继续相对于原始目录创建,而不是使用重命名或替换目录。

总结:

  • 文件目录的移动和删除不会真的从磁盘上进行解除。因为当前进程打开后拥有这个目录文件,在close后才会真的从磁盘上删除
  • 可以对应于当前删除的目录创建文件,但是不能在当前目录下的文件夹里进行操作,此时如果访问当前文件夹则会报错
  • 这样文件永远会相对于文件目录进行创建,而不是重命名的替换目录

2.open参数介绍

int open(const char* path,int oflag,...); int openat(int fd,const char* path,int oflag,...)' //特殊的fd == AT_FDCWD //oflag参数如下

  • 第一大类
    • O_RDONLY
    • O_WRONLY
    • O_RDWR
      • 大多数将这三者指定为 0 1 2 与早期的程序兼容
    • O_EXEC 只执行打开
    • O_SEARCH 只搜索打开(应用于目录)

第一大类必须指定,并且只能指定一个

  • 第二大类
    • O_APPEND 每次写都追加到文件的尾端
    • O_CLOEXEC 设置文件描述符标志,在调用exec时关闭该文件
    • O_CREATE 使用create则需指定第三个权限标记
    • O_DIRECTORT 如果不是目录则出错
    • O_EXCL 如果创建时,存在则报错
    • O_NOTTY 如果是 终端设备,则不能指定它为终端设备
    • O_NOFOLLOW 如果是符号链接文件则报错
    • O_NOBLOCK 如果是FIFO、块&字符特殊文件,则后续的IO操作都是非阻塞的
    • O_SYNC 每次write操作要等待物理IO操作完毕,包含文件属性的更新
    • O_TRUNC
    • O_TTY_INIT 打开一个还未打开的终端,设置其非标准的termios
    • O_DSYNC 每次使用write要等待物理IO操作完成,如果写不影响读取刚写入的操作, 则不等待文件属性被更新
    • O_RSYNC 每次read操作,等待同一部分的所有操作完成

二、空洞文件

1.lseek接口介绍

off_t lseek(int fd,off_t offset,int whence); whence: SEEK_SET SEEK_CUR SEEK_END

off_t的位数在有的平台下是32位,而有的是64位,可以设置

#define _FILE_OFFSET_BITS = 64

但是这种设置方式并不是可移植的

2.空洞文件

linux中lseek并不会进行任何的IO操作,对文件lseek超出当前文件大小的行为也是被允许的。

在lseek超出文件大小后进行写操作就会形成空洞文件。空洞文件中间跳跃的部分不会占用真正的磁盘位置,在访问是以0填充。

三、读写操作

1.接口

ssize_t read(int fd,void *buf,size_t nbytes);//成功返回读到字节数,读到文件尾返回0,失败返回-1 ssize_t write(int fd,const void* buf,size_t nbytes);//成功返回写入的字节数,失败返回-1

2.预读技术

不同大小的缓冲区在读写操作中,会带给程序不同的性能。将输出地重定向到 “空设备”。以下是不同缓冲区大小的时间统计

预读技术,使得读入数据时,系统试图读入更多的数据,并假设很快就用到这些数据。因此在中间部分即使缓冲区没有到达4096,这几个的读取时间也非常接近。其实此时已经预读了

四、文件的内核数据结构

1.文件的内核数据结构

v节点看起来没啥用,但其实v节点是一个中间层,为了适应不同的系统。

而Linux系统下没有 v节点,因为Linux实现了无关系统的 i节点

  • 完成write后,设置文件表项中的偏移量,如果偏移量大于i节点中的文件长度则i节点更新文件长度信息
  • 如果使用O_APPEND,则操作时,会先设置lseek到i节点中的文件长度位置,这样实现了追加操作
  • lseek只会更新文件表项中的偏移量位置

2.不同进程打开相同文件

3.父子进程文件数据结构

4.dup下的文件数据结构

五、原子操作

1.追加到一个文件

早期的linux不支持O_APPEND操作,用lseek+write来完成追加操作。

但是这两步并不原子,在过程中可能文件长度已经发生了改变(别的进程操作)。此时文件操作就会混乱。

2.原子pwrite和pread

ssize_t pread(int fd,void *buf,size_t nbytes,off_t offset); ssize_t pwrite(int fd,const void *buf,size_t nbytes,off_t offset);

  • 调用时无法中断定位和操作(原子)
  • 不更新文件偏移量

六、dup和dup2

int dup(int fd); int dup2(int fd1,int fd2);

dup和dup2之后新的文件描述符,其文件描述符标志(FD_CLOEXEC,使用execv时为1则关闭)会被清空。而只有一种情况不会被清空,当使用dup2时,两个文件描述符相同则此时不会被清空。

七、sync、fsync、fdatasync

void sync(void) void fsync(int fd); void fdatasync(int fd);

sync将修改过的快缓冲区排入写队列然后返回,不等待实际写磁盘操作结束,updat守护进程会周期性的调用sync,保证定期冲洗(flush)内核的块缓冲区。

fsync对指定数据起效,保证数据写到磁盘上才进行返回

fdatasync数据部分更新后返回,文件属性就自己等了

八、fcntl

int fcntl(int fd,int cmd,...);

1.功能概述

  1. 复制一个文件描述符(cmd=F_DUPFD,cmd= F_DUPFD_CLOEXEC)
  2. 获取/设置文件描述标志(cmd = F_GETFD,cmd = F_SETFD)
  3. 获取/设置文件状态标志 (cmd = F_GETFL,cmd = F_SETFL)
  4. 获取/设置异步IO所有权 (cmd = F_GETOWN,cmd = F_SETOWN)
  5. 获取/设置记录锁(cmd = F_GETLK,F_SETFL,F_SETLKW)

2.复制文件描述符

F_DUPFD默认也是清空的文件描述符标志,可以使用F_DUPFD_CLOEXEC,来设置复制文件描述符标志

3.获取/设置文件描述符标志

4.获取/设置文件状态标志

  1. 获取文件状态

前五个必选值,是互斥的,可以使用O_ACCMODE来判断模式

其他的直接和状态标志与,为1则是

  1. 设置文件状态

先获取再设置:|= &=~

  1. 示例

void set_fl(int fd,int mode) { int val = fcntl(fd,F_GETFL); val |= mode; if(-1 == fcntl(fd,F_SETFL,val)) { err_sys("fcntl error"); } } void clr_fl(int fd,int mode) { int val = fcntl(fd,F_GETFL); val &= ~mode; if(-1 == fcntl(fd,F_SETFL,val)) { err_sys("fcntl error"); } }

5.增加O_SYNC可能出现的问题

第一行为给空设备写,因此没有磁盘操作。其他的是对磁盘文件进行写操作

此时O_SYNC没有作用,而其他的设置明显有了对应时间上的变化.(Linux并不允许使用fcntl设置O_SYNC)

以下是mac的效果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值