> For the complete documentation index, see [llms.txt](https://lightc.gitbook.io/pwn-gitbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lightc.gitbook.io/pwn-gitbook/kpwn/kpwn-internals/xi-tong-diao-yong-yu-wen-jian-xi-tong.md).

# 系统调用与文件系统

### 系统调用

系统调用是用户空间程序与内核交互的主要机制。系统调用与普通函数调用不同，使用系统调用时，需要特殊指令以使处理器权限转换到内核态后才能调用内核里的代码。

进行系统调用参数只能为6个，`rdi/rsi/rdx/r10/r8/r9`，`rax`传递系统调用号同时也返回系统调用返回值，如果为负数（-4095\~-1）则表示产生了错误

`syscall`指令包含以下操作

先将一些保存的操作

* 将当前的`rip`的下一条指令（返回地址）存入`rcx`
* 把`rflags`标志位的值存入`rcx`

还有一些加载的操作

* 将`IA32_LSTAR MSR`寄存器的值加载到`rip`
* 把`rflags`的值与`IA32_FMASK MSR`里的值做掩码运算（屏蔽`RFLAGS`的某些标志位）
* 把`IA32_STAR MSR`寄存器里第`32~47`位加载到`CS`和`SS`段寄存器（`IA32_STAR[47:32]`高16位加载到`CS`，`IA32_STAR[47:32]`低16位加载到`SS`）（内核一般设置`CS = 0x10`（内核代码段），`SS = 0x18`（内核数据/栈段））

```c
SYM_CODE_START(entry_SYSCALL_64)
	UNWIND_HINT_ENTRY
	ENDBR

	swapgs
	/* tss.sp2 is scratch space. */
	movq	%rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
	SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
	movq	PER_CPU_VAR(cpu_current_top_of_stack), %rsp

SYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL)
	ANNOTATE_NOENDBR

	/* Construct struct pt_regs on stack */
	pushq	$__USER_DS				/* pt_regs->ss */
	pushq	PER_CPU_VAR(cpu_tss_rw + TSS_sp2)	/* pt_regs->sp */
	pushq	%r11					/* pt_regs->flags */
	pushq	$__USER_CS				/* pt_regs->cs */
	pushq	%rcx					/* pt_regs->ip */
SYM_INNER_LABEL(entry_SYSCALL_64_after_hwframe, SYM_L_GLOBAL)
	pushq	%rax					/* pt_regs->orig_ax */

	PUSH_AND_CLEAR_REGS rax=$-ENOSYS

	/* IRQs are off. */
	movq	%rsp, %rdi
	/* Sign extend the lower 32bit as syscall numbers are treated as int */
	movslq	%eax, %rsi

	/* clobbers %rax, make sure it is after saving the syscall nr */
	IBRS_ENTER
	UNTRAIN_RET
	CLEAR_BRANCH_HISTORY

	call	do_syscall_64		/* returns with IRQs disabled */
```

`swapgs`后`GS_base`变为一个内核地址`0xffff88800f400000`，然后将用户栈存在gs的一个偏移处，将cr3放入rsp然后运算得到一个新的值在放回cr3，此时cr3就指向了一个有效的地址

CR3 0x557b000 \[virtual: 0xffff888014981000]

\*CR3 0x557a000 \[virtual: 0xffff88800557a000 ◂— 0x80000000054b2067]

然后将存有用户栈的地址的低8字节处取出一个值（似乎是各种以页为单位的内存地址）放入rsp，这些地址存储着页底

```bash
pwndbg> tele 0xffff88800f40600c
00:0000│     0xffff88800f40600c ◂— 0xffffc90000260000
01:0008│     0xffff88800f406014 —▸ 0x7ffd2c041438 —▸ 0x54a25f ◂— test rax, rax
02:0010│     0xffff88800f40601c ◂— 0
03:0018│     0xffff88800f406024 ◂— 0xfffffe000000b000
04:0020│     0xffff88800f40602c ◂— 0xfffffe000000e000
05:0028│     0xffff88800f406034 ◂— 0xfffffe0000011000
06:0030│     0xffff88800f40603c ◂— 0xfffffe0000014000
07:0038│     0xffff88800f406044 ◂— 0xfffffe0000017000

pwndbg> tele 0xffffc90000260000-0x1000
00:0000│     0xffffc9000025f000 ◂— 0
... ↓     7 skipped
pwndbg> vmmap 0xfffffe000000b000-0x1000
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size  Offset File (set vmmap-prefer-relpaths on)
0xfffffe0000003000 0xfffffe0000008000 r--p     5000    3000 cpu entry
►xfffffe0000009000 0xfffffe000000b000 rw-p     2000    9000 cpu entry +0x1000
0xfffffe000000c000 0xfffffe000000e000 rw-p     2000    c000 cpu entry

pwndbg> tele 0xfffffe000000b000-0x1000
00:0000│     0xfffffe000000a000 ◂— 0
... ↓     7 skipped
pwndbg> vmmap 0xfffffe000000b000
There are no mappings for specified address or module.
pwndbg> vmmap 0xfffffe000000b000-0x1000
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size  Offset File (set vmmap-prefer-relpaths on)
0xfffffe0000003000 0xfffffe0000008000 r--p     5000    3000 cpu entry
►xfffffe0000009000 0xfffffe000000b000 rw-p     2000    9000 cpu entry +0x1000
0xfffffe000000c000 0xfffffe000000e000 rw-p     2000    c000 cpu entry
```

然后push寄存器，先push五个值，ss，用户rsp，r11(flags)，cs和用户态返回地址（rcx)

然后push各种寄存器，rax，rdi，rsi，rdx，rcx，()，r8\~r11，rbx，rbp，r12\~r15

xor edx，ecx，r8d，r9d，r10d，r11d，ebx，ebp，r12d\~r15d

将系统调用号存入rdi，栈地址赋给rsi，然后do\_syscall\_64

返回时判断栈上rsp\[+58]和\[+80]是否相同，也就是两次push的rcx，不相同则跳转到swapgs\_restore\_regs\_and\_return\_to\_usermode

然后检查cs值，在检查flags(r11)

然后pop返回，对应push的对应pop，不同的是原来push r11返回时pop rsi，push ()返回时pop rax，push rcx返回时pop rsi

pop完rsi后不pop rdi而是直接赋值mov rdi,rsp，然后将rsp转移到一开始cr3操作时存有用户栈的地址的低8字节处，push \[rdi+0x28],push \[rdi]也就是用户栈和进入系统调用时的rdi，push rax

然后将cr3放入rdi运算，然后存回cr3，然后pop rax、rdi、rsp，swapgs，sysretq

### 文件系统

[文件系统的原理 - 知乎](https://zhuanlan.zhihu.com/p/106459445)

文件系统的作用是帮助用户和操作系统高效、安全、有序地管理和访问数据

没有文件系统，读写一个文件可能需要先“遍历数据”才能找到文件并进行读写，效率极低

#### 结构

文件系统将磁盘按照内存中的页一样，将磁盘分为多个大小相同的块（block），大部分block中存储用户数据，一部分block中存储元数据（metadata，存储的数据结构称为index node，简称inode），根据inode的大小在block中分配多个inode，每次产生新数据时需要一个block存储数据和一个inode存储元数据，此时需要快速判断哪个block/inode是空闲的

block和inode可以分别使用bitmap来标记空闲状态，这些bitmap也会占据block

另外还有一个superblock，包含一个文件系统的所有控制信息，比如说magic、inodes、blocks、inodeblocks（标记第一个inode的位置）等等

Group Descriptor Table（组描述符表）先省略

#### 寻址

**ext2/ext3**

文件的大小会影响block的占据数量，这些block不一定是连续的，处理不连续的block的管理比较好的方法就是“指针”了

如果文件比较小，那么一个inode就可以存储这个文件所需的所有指针，但是如果文件比较大，那么就会使用间接索引，由inode先指向一个block，这个block再存储指向文件的指针

这叫multi-level index，类似内存管理的多级页表

**ext4**

大文件所需的metadata较多，为了解决这个问题，可以使用一个指针和长度（extents机制）表示在物理上连续的数据（文件）

#### 目录和路径

从抽象的角度，将目录视为一种文件，路径就像是树结构

对某一文件进行操作，先找到文件目录的根目录这个“文件”的inode，访问对应的block查找下一级目录“文件”的inode号，直到找到对应文件

操作系统会在内存中缓存访问的目录“文件”的inode项，第二次访问同一文件时速度会得到提升

### 虚拟文件系统VFS（virtual file system）

[Linux中的VFS实现 一 - 知乎](https://zhuanlan.zhihu.com/p/100329177)

[Linux中的VFS实现 二 - 知乎](https://zhuanlan.zhihu.com/p/107247475)

linux中，我们有不同的文件系统（比如ext4、NTFS、FAT32等等），不同文件系统的读写应该使用不同的接口函数，但是我们却总是可以使用open、read、write来操作这些不同的文件系统，这归功于**VFS**

VFS将不同的文件系统抽象成一致的行为，让用户态和内核的其他部分可以不用关注不同文件系统的不同细节

当用户调用读写的系统调用时，内核态系统调用接口处理后交给VFS处理，VFS处理后交给对应的文件系统，访问缓存，未命中则访问设备驱动，由设备驱动向硬件发送IO请求，从而实现读写操作

VFS的实现也是与文件系统类似（superblock、inode、file、dentry（记录路径名和inode的对应关系））

> superblock、inode、file、dentry是VFS的四个重要部分

在linux中，对文件的访问都是通过虚拟文件系统\*\*VFS（virtual file system）\*\*层提供的内核接口进行的

> 一个文件的inode信息可以通过stat命令获取

**访问控制**

读（r）写（w）执行（x）是文件的三种属性

尝试访问文件的用户有三类：所有者（user）、与所有者在同一用户组的用户（group）、其他用户（others）

> chmod修改权限，chown修改UID（user id）和GID（group id）

还有三种timestamp

* 文件上次被访问时间atime
* 文件内容上次被修改时间mtime
* 文件属性上次被修改时间ctime

目录和设备也被视为文件

**inode编号**

在其所属的superblock中有唯一编号，即`i_ino`

**superblock**

`i_sb`指向其所属的superblock

**双向链表**

`i_sb_list`（list\_head包含两个指向list\_head的指针）指向其在双向链表中的下一个和上一个inode结构体

**open**

当进程需要读取写入文件时，使用了open

```c
int open (const char *pathname, int flags, mode_t mode);
```

内核先将传入的路径名提供给dentry，找到文件对应的inode

> 事实上，dentry是一个只在内存中存在的事物，它缓存了磁盘文件查找的结果，磁盘上的目录只是简单的目录项`dir_entry`，当读取一个新文件时，内核读取其上一级目录的目录块，找到新文件的inode号（没有对应文件也会创建一个dentry，只是这个dentry的`d_inode`设为NULL），创建一个dentry结构体在内存中，设置其内容，放入全局dcache的哈希表中（长时间或内存不足未使用则会从哈希表移除并释放其内存）（仍在使用中的文件被删除可能直接在dentry中标记d\_inode为NULL，详看rm）

> hard link会将inode的i\_dentry和一个或多个dentry的d\_alias形成双向链表

**hard link**

> 通过`ln <file> <link>`创建

这样创建的链接文件的inode编号和原文件相同`ls -i`

事实上，一个文件的hard link增加的是一个新的一个dentry项并将该项与原文件关联的inode关联，增加的是对这个inode结构体的关联/指向

因此其通过`ls -i`命令展示的`-rw-rw-r-- <__i_nlink> <size>`

其的权限和size完全与原文件相同，是因为这两个文件的dentry关联的是同一个inode，因此如果硬链接文件进入其他文件系统，则可能会与该文件系统既有的文件inode发生冲突

**symbolic/soft link**

> 通过`ln -s <file> <link>`创建

而symbolic/soft link则是创建了一个文件，存放了原文件的路径字符串

当访问一个软链接文件时，VFS通过dentry找到对应的inode，发现文件类型是软链接，从数据块中读出路径字符串（如果路径很短，则会直接存在inode中的i\_link），再次解析

**rm**

使用rm命令删除文件时，先操作磁盘，移除`dir_entry`，将dentry和inode的关联`unlink`（也就是将对应的inode的`__i_nlink`减1），然后找到内存中的dentry，调用其`d_op`的处理`d_delete`，将其从`dcache hash`中擦除，处理引用计数，如果没有进程打开该文件，则引用计数归零，然后判断引用计数，此时引用计数为零则会立即释放，如果引用计数不为零则不会立即释放文件，会变成一个`disconnect/negative`的状态，此时`d_inode`可能不变也可能置空，内容仍然保存在磁盘上

dentry结构体被定义在`/include/linux/dcache.h`中，其结构体如下

```c
struct dentry {
	/* RCU lookup touched fields */
	unsigned int d_flags;		/* protected by d_lock */
	seqcount_spinlock_t d_seq;	/* per dentry seqlock */
	struct hlist_bl_node d_hash;	/* lookup hash list */
	struct dentry *d_parent;	/* parent directory */
	union {
	struct qstr __d_name;		/* for use ONLY in fs/dcache.c */
	const struct qstr d_name;
	};
	struct inode *d_inode;		/* Where the name belongs to - NULL is
					 * negative */
	union shortname_store d_shortname;
	/* --- cacheline 1 boundary (64 bytes) was 32 bytes ago --- */

	/* Ref lookup also touches following */
	const struct dentry_operations *d_op;
	struct super_block *d_sb;	/* The root of the dentry tree */
	unsigned long d_time;		/* used by d_revalidate */
	void *d_fsdata;			/* fs-specific data */
	/* --- cacheline 2 boundary (128 bytes) --- */
	struct lockref d_lockref;	/* per-dentry lock and refcount
					 * keep separate from RCU lookup area if
					 * possible!
					 */

	union {
		struct list_head d_lru;		/* LRU list */
		wait_queue_head_t *d_wait;	/* in-lookup ones only */
	};
	struct hlist_node d_sib;	/* child of parent list */
	struct hlist_head d_children;	/* our children */
	/*
	 * d_alias and d_rcu can share memory
	 */
	union {
		struct hlist_node d_alias;	/* inode alias list */
		struct hlist_bl_node d_in_lookup_hash;	/* only for in-lookup ones */
	 	struct rcu_head d_rcu;
	} d_u;
};
```

inode结构体被定义在`/include/linux/fs.h`中，其结构体如下

```c
struct inode {
	umode_t			i_mode;					// 文件类型和权限
	unsigned short		i_opflags;			// 标志位
	unsigned int		i_flags;
#ifdef CONFIG_FS_POSIX_ACL
	struct posix_acl	*i_acl;
	struct posix_acl	*i_default_acl;
#endif
	kuid_t			i_uid;
	kgid_t			i_gid;

	const struct inode_operations	*i_op;	// inode操作表
	struct super_block	*i_sb;				// 所属superblock
	struct address_space	*i_mapping;		// 缓存页

#ifdef CONFIG_SECURITY
	void			*i_security;
#endif

	/* Stat data, not accessed from path walking */
	unsigned long		i_ino;				// inode编号
	/*
	 * Filesystems may only read i_nlink directly.  They shall use the
	 * following functions for modification:
	 *
	 *    (set|clear|inc|drop)_nlink
	 *    inode_(inc|dec)_link_count
	 */
	union {
		const unsigned int i_nlink;			// 链接计数
		unsigned int __i_nlink;
	};
	dev_t			i_rdev;					// 设备号
	loff_t			i_size;					// 文件大小
	time64_t		i_atime_sec;
	time64_t		i_mtime_sec;
	time64_t		i_ctime_sec;
	u32			i_atime_nsec;
	u32			i_mtime_nsec;
	u32			i_ctime_nsec;
	u32			i_generation;
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	unsigned short          i_bytes;		// 最后一个未填满的块实际使用字节数
	u8			i_blkbits;
	enum rw_hint		i_write_hint;
	blkcnt_t		i_blocks;				// 占用块数

#ifdef __NEED_I_SIZE_ORDERED
	seqcount_t		i_size_seqcount;
#endif

	/* Misc */
	struct inode_state_flags i_state;
	/* 32-bit hole */
	struct rw_semaphore	i_rwsem;

	unsigned long		dirtied_when;	/* jiffies of first dirtying */
	unsigned long		dirtied_time_when;

	struct hlist_node	i_hash;
	struct list_head	i_io_list;	/* backing dev IO list */
#ifdef CONFIG_CGROUP_WRITEBACK
	struct bdi_writeback	*i_wb;		/* the associated cgroup wb */

	/* foreign inode detection, see wbc_detach_inode() */
	int			i_wb_frn_winner;
	u16			i_wb_frn_avg_time;
	u16			i_wb_frn_history;
#endif
	struct list_head	i_lru;		/* inode LRU list */	// least recent used 内核回收长时间未使用inode
	struct list_head	i_sb_list;
	struct list_head	i_wb_list;	/* backing dev writeback list */
	union {
		struct hlist_head	i_dentry;		// 关联的dentry
		struct rcu_head		i_rcu;
	};
	atomic64_t		i_version;
	atomic64_t		i_sequence; /* see futex */
	atomic_t		i_count;				// 引用计数
	atomic_t		i_dio_count;
	atomic_t		i_writecount;
#if defined(CONFIG_IMA) || defined(CONFIG_FILE_LOCKING)
	atomic_t		i_readcount; /* struct files open RO */
#endif
	union {
		const struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
		void (*free_inode)(struct inode *);
	};
	struct file_lock_context	*i_flctx;
	struct address_space	i_data;
	union {
		struct list_head	i_devices;
		int			i_linklen;
	};
	union {
		struct pipe_inode_info	*i_pipe;
		struct cdev		*i_cdev;
		char			*i_link;
		unsigned		i_dir_seq;
	};


#ifdef CONFIG_FSNOTIFY
	__u32			i_fsnotify_mask; /* all events this inode cares about */
	/* 32-bit hole reserved for expanding i_fsnotify_mask */
	struct fsnotify_mark_connector __rcu	*i_fsnotify_marks;
#endif

	void			*i_private; /* fs or device private pointer */
} __randomize_layout;
```

### 用户态文件系统 FUSE（Filesystem in Userspace）

[用户态文件系统 - FUSE - 知乎](https://zhuanlan.zhihu.com/p/143256077)

用户态文件系统是指一个文件系统的data和metadata（元数据，即描述数据的数据）都是由用户态的进程提供的（这种进程被称为"**daemon（守护进程）**"）。

对于micro-kernel（将操作系统核心功能置于内核空间而将大部分服务拆分为用户态进程实现，依赖内核模块作为代理进行通信）的操作系统来说，在用户态实现文件系统不算什么，但对于monolithic-kernel（大部分系统功能都需要集中在一个内核空间执行，依赖内核内部的统一接口调度）的Linux来说，意义就有所不同。
