ls 是 Linux 和 Unix 下最常使用的命令之一,主要用来列举目录下的文件信息,-l 参数允许查看当前目录下所有可见文件的详细属性,包括文件属性、所有者、文件大小等信息。但是,当其显示符号链接的属性时,无论其指向文件属性如何,都会显示 777,即任何人可读可写可执行。本文从 ls 命令源码出发,由浅入深地分析该现象的原因,简略探究了 Linux 4.10 下的符号链接链接、文件系统与权限的源码实现。
关键词:Linux ls 符号链接 文件系统 权限 源码分析
在 Linux 中每个文件有所有者、所在组、其它组的概念 [11]。所有者一般为文件的创建者,谁创建了该文件,就天然的成为该文件的所有者;当某个用户创建了一个文件后,这个文件的所在组就是该用户所在的组;除开文件的所有者和所在组的用户外,系统的其它用户都是文件的其它组。ls 命令将每个由 Directory 参数指定的目录或者每个由 File 参数指定的名称写到标准输出,以及所要求的和标志一起的其它信息。ls -l 中显示的内容常如下所示:
-rwxrw-r‐-1 root root 1213 Feb 2 09:39 abc |
前 10 个字符说明了文件类型与权限。第一个字符代表文件(-)、目录(d),链接(l),其余字符每 3 个一组(rwx),读(r)、写(w)、执行(x)。第一组 rwx:文件所有者的权限是读、写和执行;第二组 rw-:与文件所有者同一组的用户的权限是读、写但不能执行;第三组 r--:不与文件所有者同组的其他用户的权限是读不能写和执行。权限也可用数字表示为:r=4,w=2,x=1 因此 rwx=4+2+1=7。
如前所述,若第一个字符显示为 l,说明该文件是符号链接。符号链接(软链接)是一类特殊的文件, 其包含有一条以绝对路径或者相对路径的形式指向其它文件或者目录的引用 [12]。符号链接的操作是透明的:对符号链接文件进行读写的程序会表现得直接对目标文件进行操作。某些需要特别处理符号链接的程序(如备份程序)可能会识别并直接对其进行操作。一个符号链接文件仅包含有一个文本字符串,其被操作系统解释为一条指向另一个文件或者目录的路径。它是一个独立文件,其存在并不依赖于目标文件。如果删除一个符号链接,它指向的目标文件不受影响。如果目标文件被移动、重命名或者删除,任何指向它的符号链接仍然存在,但是它们将会指向一个不复存在的文件。这种情况被有时被称为被遗弃。
但是,我们常常发现,创建符号链接其权限就会显示为 lrwxrwxrwx,为什么?是 ls 命令对符号链接进行了处理,还是文件本身权限即如此?这样会不会带来一些安全隐患?怀着这些问题,本文由浅入深,从 ls 命令出发,探索了其背后的系统调用至 vfs 文件系统实现细节,力求解释这些问题。但作者水平有限,尚有很多细节不清楚,不对之处恳请批评指正。
ls 命令是 Linux shell 下最常用的命令之一,主要用来列举目录下的文件信息。经过搜索引擎查找 [1],要查看该命令的源代码需要下载对应软件包 coreutils 的源代码。其实只要知道了软件包的名字,既可以按照文献[1] 的方法使用 apt-get source 下载,也可以从软件包 coreutils 的官网 [2] 下载。下载完毕后,使用 source insight 软件建立工程,即可方便地开始源码阅读。本文使用截止 2017 年 2 月 18 日最新版本的 coreutils-8.26。
打开 / src/ls.c,从 main 函数开始,忽略开始的初始化、颜色设定等内容,1451 行调用的 decode_switches 对参数进行了一些处理,由于研究的是 ls 程序,所以第一个 switch(ls_mode) 关注 LS_LS。接下来,1703 行设置 dereference = DEREF_UNDEFINED。关键部分代码为 1752 行的一个 switch(true),它根据传入的参数,设置相应的标志,如 - l 设置 format = long_format,H、I、L 设置了 dereference 的一些模式,由于作者平时经常使用的是 ls –l,所以仅关注 - l 选项下的情况,dereference 仍然为 DEREF_UNDEFINED。同时,该函数 2125 行对 format == long_format 的情况,做了一些格式输出上的工作。
发现 1467 行对 dereference 变量的判断影响了如何处理符号链接。若仅使用 - l 选项,dereference 赋值为 DEREF_NEVER,即仅仅拷贝复制符号链接自身。
若设置了递归枚举,设置一个哈希表来检测是否出现了目录环。接下来开辟 cwd_file 变量空间,其是指向 fileinfo 结构体的向量指针,保存了要描述的文件。关于 fileinfo 结构体源码中已经给了很好的注释,其中 struct stat 类型的变量 stat 具体描述了文件的信息,往往由 stat() 或 lstat() 函数返回。struct stat 类型的定义可以在 Linux 源码 include\uapi\asm-generic 中找到,可以看出新版本 64 位中与常见文档中相比增加了许多 pad 填充,并将类型的一些宏定义取消了,直接采用了 unsigned long。
下面主要调用了 gobble_file 函数,添加文件到当前的文件表中,即放到 cwd_file 中未使用的第一个位置上。3131 行的 switch 语句根据 dereference 值,调用 stat() 或 lstat() 函数,由上面分析可知,ls –l 是 DEREF_NEVER,故调用 lstat() 函数,并将结果存入 fileinfo.stat 中(代码中是变量 f);接下来,代码再根据结构体 fileinfo.stat,对 fileinfo 其他部分赋值,在 long_format 情况下,fileinfo. scontext 是 SELinux 有关函数 lgetfilecon() 获得的安全上下文(context)。同时也可以看出,默认 - l 选项是不对符号链接进行追踪的,所以调用的函数也都是对应版本。3235 行判断当文件为符号链接且模式为 long_format 时会成立,由此赋值了 fileinfo 的 linkok、linkmode 等值。接下来函数主要根据要输出哪些信息,将 fileinfo 中的值保存了下来。
最后,在 main 函数 1538 行,根据文件的数目,调用 print_current_files () 来输出文件内容,print_long_format() 中第 3967 行通过 filemodestring() 函数将文件的读写执行权限填入了 modebuf,该函数在 filemode.c 中定义。在填入时,ls 程序未对符号链接做特殊处理,由此可见,符号链接权限问题的关键在于 lstat() 函数的实现是如何填入 stat 结构体中 st_mode 的。
stat 是用来获得文件信息的系统调用 [3],要寻找该系统调用的源代码,首先要理解系统调用的流程。这里参考了文献 [4][5],根据自己理解,C 语言调用 stat 函数时,调用的是 C 库对该函数的实现,接着执行库中函数的具体实现的代码,其中非常关键的一句代码就是 int 0x80,中断使得进程从用户态切换到内核态,中断处理程序然后开始执行内核中对应 80 号中断的系统调用处理程序的代码 system_call;system_call 系统调用处理程序就根据传入的系统调用号从系统调用服务程序数组中寻找对应系统调用服务程序,最后执行完成后按照调用顺序的相反顺序一步步返回结果。
根据一般规律,系统调用定义的名字就是在函数前面加一个 sys_,由此在 include\linux\syscalls.h 中发现了一系列 stat 的声明,而 fs\Stat.c 中是对应的定义。(内核中使用 SYSCALL_DEFINE2 的宏定义来定义系统调用,展开就是声明的形式。)这里会发现,4.10 内核中同时存在 newstat 与 stat,无论新旧,实现都是使用了 vfs_stat 函数,传入参数为 kstat,差别在于宏倒数第二个参数的类型。(但是这个参数具体有什么作用?实现中好像并没有用到这个参数。)
接下来需要看 vfs_lstat 的实现,他与 vfs_stat 都是调用了 vfs_fstatat,区别在于 vfs_lstat 给最后一个参数赋值为了 AT_SYMLINK_NOFOLLOW,说明不要追踪符号链接。
vfs_fstatat 首先对 flag 参数进行检查,必须有(并非等于)规定的几种标识之一;然后调用 user_path_at,根据返回结果再调用 vfs_getattr 和 path_put;之后看似是一个错误处理,会返回到 retry,没有错误则函数退出返回。对关键数据 stat 赋值的部分应该就在 vfs_getattr 函数了。
为了进行后面的分析,这里需要 Linux 内核文件系统有一定的了解 [6][7]。Linux 有着极其丰富的文件系统,大体上可分如下几类:
网络文件系统,如 nfs、cifs 等;
磁盘文件系统,如 ext4、ext3 等;
特殊文件系统,如 proc、sysfs、ramfs、tmpfs 等。
实现以上这些文件系统并在 Linux 下共存的基础就是 Linux VFS(Virtual File System 又称 Virtual Filesystem Switch),即虚拟文件系统。VFS 作为一个通用的文件系统,抽象了文件系统的四个基本概念:文件、目录项 (dentry)、索引节点 (inode) 及挂载点,其在内核中为用户空间层的文件系统提供了相关的接口。VFS 实现了 open()、read() 、stat() 等系统调并使得 cp 等用户空间程序可跨文件系统。VFS 真正实现了上述内容中:在 Linux 中除进程之外一切皆是文件。
Linux VFS 存在四个基本对象:超级块对象 (superblock object)、索引节点对象 (inode object)、目录项对象 (dentry object) 及文件对象 (file object)。超级块对象代表一个已安装的文件系统;索引节点对象代表一个文件;目录项对象代表一个目录项,如设备文件 event5 在路径 /dev/input/event5 中,其存在四个目录项对象:/ 、dev/ 、input/ 及 event5。文件对象代表由进程打开的文件。这四个对象与进程及磁盘文件间的关系如图,其中 d_inode 即为硬链接。为文件路径的快速解析,Linux VFS 设计了目录项缓存(Directory Entry Cache,即 dcache)。下面列出几个关键数据结构,并在关注的部分给出注释。
View Code
- 1 struct nameidata { //文件查找临时结构体
- 2
- 3 struct path path;//包含vfsmount挂载点和dentry目录项
- 4
- 5 struct qstr last;
- 6
- 7 struct path root;
- 8
- 9 struct inode *inode; /* path.dentry.d_inode */
- 10
- 11 unsigned int flags;
- 12
- 13 unsigned seq, m_seq;
- 14
- 15 int last_type; /*路径中的最后一个component的类型*/
- 16
- 17 unsigned depth; //符号链接嵌套的级别
- 18
- 19 int total_link_count;
- 20
- 21 struct saved {
- 22
- 23 struct path link;
- 24
- 25 struct delayed_call done;
- 26
- 27 const char *name;
- 28
- 29 unsigned seq;
- 30
- 31 } *stack, internal[EMBEDDED_LEVELS];
- 32
- 33 struct filename *name; //保存要查找的文件名
- 34
- 35 struct nameidata *saved;
- 36
- 37 struct inode *link_inode;
- 38
- 39 unsigned root_seq;
- 40
- 41 int dfd;
- 42
- 43 };
- 44
- 45 struct dentry {//目录项对象
- 46
- 47 /* RCU lookup touched fields */
- 48
- 49 unsigned int d_flags; /* protected by d_lock */
- 50
- 51 seqcount_t d_seq; /* per dentry seqlock */
- 52
- 53 struct hlist_bl_node d_hash; /* lookup hash list */
- 54
- 55 struct dentry *d_parent; /* parent directory */
- 56
- 57 struct qstr d_name;
- 58
- 59 struct inode *d_inode; /* Where the name belongs to - NULL is
- 60
- 61 * negative */
- 62
- 63 unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
- 64
- 65
- 66
- 67 /* Ref lookup also touches following */
- 68
- 69 struct lockref d_lockref; /* per-dentry lock and refcount */
- 70
- 71 const struct dentry_operations *d_op;//目录项方法
- 72
- 73 struct super_block *d_sb; /* The root of the dentry tree */
- 74
- 75 unsigned long d_time; /* used by d_revalidate */
- 76
- 77 void *d_fsdata; /* fs-specific data */
- 78
- 79
- 80
- 81 union {
- 82
- 83 struct list_head d_lru; /* LRU list */
- 84
- 85 wait_queue_head_t *d_wait; /* in-lookup ones only */
- 86
- 87 };
- 88
- 89 struct list_head d_child; /* child of parent list */
- 90
- 91 struct list_head d_subdirs; /* our children */
- 92
- 93 /*
- 94
- 95 * d_alias and d_rcu can share memory
- 96
- 97 */
- 98
- 99 union {
- 100
- 101 struct hlist_node d_alias; /* inode alias list */
- 102
- 103 struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
- 104
- 105 struct rcu_head d_rcu;
- 106
- 107 } d_u;
- 108
- 109 };
- 110
- 111 struct inode { //索引节点
- 112
- 113 umode_t i_mode; //文件类型与访问权限,是本文所关注的重点部分
- 114
- 115 unsigned short i_opflags;//2.6内核中没有的字段,哪里去找这个字段注释?
- 116
- 117 kuid_t i_uid;
- 118
- 119 kgid_t i_gid;
- 120
- 121 unsigned int i_flags;
- 122
- 123
- 124
- 125 #ifdef CONFIG_FS_POSIX_ACL
- 126
- 127 struct posix_acl *i_acl;
- 128
- 129 struct posix_acl *i_default_acl;
- 130
- 131 #endif
- 132
- 133
- 134
- 135 const struct inode_operations *i_op; //索引节点的操作
- 136
- 137 struct super_block *i_sb;
- 138
- 139 struct address_space *i_mapping;
- 140
- 141
- 142
- 143 #ifdef CONFIG_SECURITY
- 144
- 145 void *i_security;
- 146
- 147 #endif
- 148
- 149
- 150
- 151 /* Stat data, not accessed from path walking */
- 152
- 153 unsigned long i_ino;
- 154
- 155 /*
- 156
- 157 * Filesystems may only read i_nlink directly. They shall use the
- 158
- 159 * following functions for modification:
- 160
- 161 *
- 162
- 163 * (set|clear|inc|drop)_nlink
- 164
- 165 * inode_(inc|dec)_link_count
- 166
- 167 */
- 168
- 169 union {
- 170
- 171 const unsigned int i_nlink;
- 172
- 173 unsigned int __i_nlink;
- 174
- 175 };
- 176
- 177 dev_t i_rdev;
- 178
- 179 loff_t i_size;
- 180
- 181 struct timespec i_atime;
- 182
- 183 struct timespec i_mtime;
- 184
- 185 struct timespec i_ctime;
- 186
- 187 spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
- 188
- 189 unsigned short i_bytes;
- 190
- 191 unsigned int i_blkbits;
- 192
- 193 blkcnt_t i_blocks;
- 194
- 195
- 196
- 197 #ifdef __NEED_I_SIZE_ORDERED
- 198
- 199 seqcount_t i_size_seqcount;
- 200
- 201 #endif
- 202
- 203
- 204
- 205 /* Misc */
- 206
- 207 unsigned long i_state;
- 208
- 209 struct rw_semaphore i_rwsem;
- 210
- 211
- 212
- 213 unsigned long dirtied_when; /* jiffies of first dirtying */
- 214
- 215 unsigned long dirtied_time_when;
- 216
- 217
- 218
- 219 struct hlist_node i_hash;
- 220
- 221 struct list_head i_io_list; /* backing dev IO list */
- 222
- 223 #ifdef CONFIG_CGROUP_WRITEBACK
- 224
- 225 struct bdi_writeback *i_wb; /* the associated cgroup wb */
- 226
- 227
- 228
- 229 /* foreign inode detection, see wbc_detach_inode() */
- 230
- 231 int i_wb_frn_winner;
- 232
- 233 u16 i_wb_frn_avg_time;
- 234
- 235 u16 i_wb_frn_history;
- 236
- 237 #endif
- 238
- 239 struct list_head i_lru; /* inode LRU list */
- 240
- 241 struct list_head i_sb_list;
- 242
- 243 struct list_head i_wb_list; /* backing dev writeback list */
- 244
- 245 union {
- 246
- 247 struct hlist_head i_dentry;
- 248
- 249 struct rcu_head i_rcu;
- 250
- 251 };
- 252
- 253 u64 i_version;
- 254
- 255 atomic_t i_count;
- 256
- 257 atomic_t i_dio_count;
- 258
- 259 atomic_t i_writecount;
- 260
- 261 #ifdef CONFIG_IMA
- 262
- 263 atomic_t i_readcount; /* struct files open RO */
- 264
- 265 #endif
- 266
- 267 const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
- 268
- 269 struct file_lock_context *i_flctx;
- 270
- 271 struct address_space i_data;
- 272
- 273 struct list_head i_devices;
- 274
- 275 union {
- 276
- 277 struct pipe_inode_info *i_pipe;
- 278
- 279 struct block_device *i_bdev;
- 280
- 281 struct cdev *i_cdev;
- 282
- 283 char *i_link;
- 284
- 285 unsigned i_dir_seq;
- 286
- 287 };
- 288
- 289
- 290
- 291 __u32 i_generation;
- 292
- 293
- 294
- 295 #ifdef CONFIG_FSNOTIFY
- 296
- 297 __u32 i_fsnotify_mask; /* all events this inode cares about */
- 298
- 299 struct hlist_head i_fsnotify_marks;
- 300
- 301 #endif
- 302
- 303
- 304
- 305 #if IS_ENABLED(CONFIG_FS_ENCRYPTION)
- 306
- 307 struct fscrypt_info *i_crypt_info;
- 308
- 309 #endif
- 310
- 311
- 312
- 313 void *i_private; /* fs or device private pointer */
- 314
- 315 };
- 316
- 317
- 318
- 319 struct file { //文件对象,描述进程怎样与一个打开的文件进行交互
- 320
- 321 union {
- 322
- 323 struct llist_node fu_llist;
- 324
- 325 struct rcu_head fu_rcuhead;
- 326
- 327 } f_u;
- 328
- 329 struct path f_path;
- 330
- 331 struct inode *f_inode; /* cached value */
- 332
- 333 const struct file_operations *f_op;
- 334
- 335
- 336
- 337 /*
- 338
- 339 * Protects f_ep_links, f_flags.
- 340
- 341 * Must not be taken from IRQ context.
- 342
- 343 */
- 344
- 345 spinlock_t f_lock;
- 346
- 347 atomic_long_t f_count;
- 348
- 349 unsigned int f_flags;
- 350
- 351 fmode_t f_mode;
- 352
- 353 struct mutex f_pos_lock;
- 354
- 355 loff_t f_pos; //文件偏移
- 356
- 357 struct fown_struct f_owner;
- 358
- 359 const struct cred *f_cred; //进程相关安全上下文信息,如uid、权限等
- 360
- 361 struct file_ra_state f_ra;
- 362
- 363
- 364
- 365 u64 f_version;
- 366
- 367 #ifdef CONFIG_SECURITY
- 368
- 369 void *f_security;
- 370
- 371 #endif
- 372
- 373 /* needed for tty driver, and maybe others */
- 374
- 375 void *private_data;
- 376
- 377
- 378
- 379 #ifdef CONFIG_EPOLL
- 380
- 381 /* Used by fs/eventpoll.c to link all the hooks to this file */
- 382
- 383 struct list_head f_ep_links;
- 384
- 385 struct list_head f_tfile_llink;
- 386
- 387 #endif /* #ifdef CONFIG_EPOLL */
- 388
- 389 struct address_space *f_mapping;
- 390
- 391 } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
有了基础补充后,继续 4.0.1 节分析系统调用。user_path_at 调用并返回 user_path_at_empty 的返回值,user_path_at_empty 返回 filename_lookup 的返回值,传参数时使用了 getname_flags 函数(实在看不懂这个函数,但是 2.6 内核要简洁的多,直接返回 char*,4.10 多设计了一个 filename *)将用户传入的文件名 const char __user *name 转化为了 struct filename * 类型,其中__user 宏定义为空的, 可能是为了标记该参数由用户传入吧。(为什么要一层一层来调用,不怕效率低吗?)
filename_lookup 主要调用了 path_lookupat 函数,在之前先调用了 set_nameidata,对 nameidata 进行了一些初始化赋值,将要查询的文件名放入了结构体 nameidata 中,并且将从 current 取出的 nameidata 保存下来,组成了一个链表。(这里发现 current 是宏定义,开始的时候 source insight 自动追踪变成了循环宏定义,应该是追踪错了头文件,因为有许多个 current.h。后来查阅资料看是获得调用系统调用进程的数据结构信息。)
接着进入 path_lookupat 函数,此时参数为 0 | LOOKUP_RCU,即 LOOKUP_ RCU。(Read-Copy Update) 是内核数据同步的一种锁机制。
1. 函数首先调用 path_init() 函数,path_init() 函数主要是初始化查询,将 nd 实例的 mnt 和 dentry 成员设置为根目录或者工作目录的对应项
a,绝对路径 (以/开始),获得根目录的 dentry。它存储在 task_struct 中 fs 指向的 fs_struct 结构中。task_struct->fs_struct.root 。
b,相对路径,直接从当前进程 task_struct 结构中的获得指针 fs,它指向的一个 fs_struct,fs_struct 中有一个指向 "当前工作目录" 的 dentry。
2,path_lookupat() 然后循环调用 link_path_walk() 函数。link_path_walk() 函数将传入的路径名转化为 dentry 目录项:
首先跳过路径名的'/',如果只有'/'则直接返回 0;得到正确的路径名后,进入一个循环,每次都调用 may_lookup() 函数对 inode 节点做权限检查,如果权限不够也直接返回 fail,在 Unix 中,只有目录是可执行的,它才可以被遍历;接下来计算的哈希与目录项高速缓存有关;该循环不断更新 last_type 和 last,如果是最后一部分的返回,若不是则调用 walk_component() 函数。walk_component 先处理 LAST_DOTS,若发现 LAST_NORM 类型,即普通文件,则调用 lookup_fast() 在缓存中查找,若没有好的结果则调用 lookup_slow(),获得 i_mutex,重新检查缓存并向文件系统查找,他们都会调用 follow_managed 来处理挂载点;若有必要,期间 walk_component 会更新 nameidata 结构体的 path。
link_path_walk 会随着文件路径每部分深入,并追踪其间遇到的每个符号链接,直到其到达最后一部分,返回给 nameidata.last。这个 link_path_walk()函数本身非常复杂,也比较难懂,细节内容可参考文献 [8][9][13],[13] 对源码的注释非常清楚。link_path_walk 函数主要是根据给定的路径,找到最后一个路径分量的目录项对象和安装点。
3. 在循环中还会调用 trailing_symlink() 函数来继续追踪最后部分的符号链接。trailing_symlink() 会先调用 may_follow_link(),这个函数检查符号链接的一些不安全权限情况。接着调用 get_link(),先更新相关的访问时间等信息,然后调用 inode 中的 get_link() 方法完成符号链接解析;注意,原来的 follow_link 被 get_link 代替,而 put_link,通过在 get_link 中设置 set_delayed_call 代替。
4. 最后对 nd 进行一些恢复收尾工作。
audit_inode 调用了__audit_inode,定义在 kernel/auditsc.c 中,保存查找的 inode 和 device,从名字猜测是审计用,这里先不关心。然后恢复 current->nameidata,并释放 name 内存,与开头对应。这样 filename_lookup 结束,返回 retval 变量至 user_path_at 函数,即返回 path_lookupat 的结果。由此可见 user_path_at 就是检查是否存在这个文件,以及相关权限是否允许。
security_inode_getattr 定义在 security/security.c,首先检查 dentry 的 inode 的 i_flags 是否为 S_PRIVATE,即 inode 文件系统的安装标识。若不是,则调用 call_int_hook(inode_getattr, 0, path),这个宏定义就看不懂了,涉及到 security_hook_heads 的很多东西,这里先忽略。
注释说明是在没有安全检查的情况下获得属性,即没有调用 security_inode_getattr,实现很简单,从 dentry 目录项中获得 inode,然后调用 inode 索引节点对象的 getattr 方法,再用 generic_fillattr 填充到返回的 stat 中,stat->mode=inode->i_mode。Inode 方法中保存的函数指针,就指向了每个具体文件操作系统的的函数。
通过分析,ls –l 获得的符号链接就是 vfs 下层文件系统 getattr 返回的信息,那么下层文件系统 getattr 如何实现?这取决于不同的具体文件系统,《深入理解 Linux 内核》中提到 ext2 使用 generic_getattr,但是在 4.10 源码中已经难以寻找到了,再深入的内容需要额外的耐心。那么如何查看 inode 的信息呢?Debugfs 是一种特殊的文件系统,提供了把内核信息传递到用户空间的方式,与 / proc 类似。在 debugfs 中执行 mi 命令 + 要查看的文件,可以得到完整的 inode 信息。如下图所示,符号链接 inode 中的 mode 值确实为 0120777。这会不会有什么安全隐患,为什么要这样设计?
文献 [10] 也有这样的描述 "Symbolic links (sometimes called soft links) do not link to inodes, but create a name to name mapping. Symbolic links are created with ln -s. As you can see below, the symbolic link gets an inode of its own. Permissions on a symbolic link have no meaning, since the permissions of the target apply. Hard links are limited to their own partition (because they point to an inode), symbolic links can link anywhere (other file systems, even networked)."大意为" 符号链接不链接到 inode 结点,而是创建名字到名字的映射。符号链接拥有自己的 inode 结点,其权限是没有意义的,因为应用的是链接目标文件的权限。符号链接可以链接至任何地方,如跨文件系统,甚至网络 "。在 path_lookupat 查找路径时,已经对目录进行了权限检查,一般情况,如 open 系统调用,在路径寻找的时候都会使用 do_follow_link 函数来自动解析掉符号链接,(之前分析的 path_lookupat 循环中 trailing_symlink 也是用来追踪最后一个分量 dentry 为符号链接的情况,但应该由于设置了 falg 没有继续追踪符号链接。奇怪的是根据 flag 没有发现可以不调用 trailing_symlink。)所以符号链接本身的权限没有意义;对文件系统详细分析的优秀文档可见[14]。
但是,在分析源码过程中见到的 may_follow_link() 函数就考虑了一些可能的安全隐患。此外,假想一种场景,若系统或 A app 想使用 B app 的凭证文件 key,恶意的 B app 可以将 key 这个文件符号链接至其他任何地方,如 C app 的凭证,会不会引发这样的一些问题?仍待探索。
1. 想看懂 Linux 内核源码,甚至只是 filesystem 这一部分的源码,都需要对整个 Linux 内核源码有一定的认识。如遇到的系统调用,进程相关 current,安全相关 security.c 的内容。
2. 看源码前一定尽力找文档!看源码前一定尽力找文档!看源码前一定尽力找文档!开始作者仅根据《深入理解 Linux 内核》这本书在探索 path_lookupat,发现 4.10 与书上的 2.6 内核有不少的差别,所以一定要参考源码目录下的 document 目录下的文档(没有国人翻译成中文吗?),里面讲了源码的实现细节,并且和现役版本对应。另外也可以搜索其他人分析源码的博客,但往往比较旧。
3. 不要完全相信 source insight 的自动查找,遇见问题要相信自己的眼睛,再结合网上的 Linux 源码索引,优先用 google 不要用百度。
4. 使用 source insight 建立工程太大的话同步符号要好久,所以不妨先只加入自己关注的部分,比如只加入 fs 文件夹下的源码,这样效率比较高。
5. 对内核中使用的数据结构和设计思路越清楚,越有利于看懂源码。
6. 有了一定基础后,看懂源码不是梦,但非常非常需要耐心与时间,尤其是细节部分。由于自己最近耐心不佳,基础也欠缺,所以没有在意太多细节,仅力求了解全貌。
[1] Linux 命令源码的查看.
[2] Coreutils - GNU core utilities.
[3] stat (C System Call). http://codewiki.wikidot.com/c:system-calls:stat
[4] Linux 系统调用内核源码分析.
[5] Linux 系统调用 (syscall) 原理. http://gityuan.com/2016/05/21/syscall/
[6] 理解 Linux 的硬链接与软链接.
[7] Linux 的虚拟文件系统 -- 各结构之间的联系.
[8] link_path_walk() 路径名查找. http://blog.chinaunix.net/uid-12567959-id-160996.html
[9] 《深入理解 Linux 内核》P495-P504
[10] Chapter 9. file links.
[11] Linux 下用户组、文件权限详解.
[12] 符号链接. https://zh.wikipedia.org/zh / 符号链接
[13] Linux 文件系统 (七)--- 系统调用之 open 操作 (三) 之 open_namei 函数.
[14] linux 内核 follow_link 分析. http://blog.csdn.net/sanwenyublog/article/details/50856837
来源: http://www.cnblogs.com/ascii0x03/p/6442420.html