Linux下ls命令显示符号链接权限为777的探索

Posted ASCII0x03

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux下ls命令显示符号链接权限为777的探索相关的知识,希望对你有一定的参考价值。

Linux下ls命令显示符号链接权限为777的探索

                                               ——深入ls、链接、文件系统与权限         

                                                                                                       by Ascii0x03,http://www.cnblogs.com/ascii0x03/p/6442420.html

一、摘要

ls是Linux和Unix下最常使用的命令之一,主要用来列举目录下的文件信息,-l参数允许查看当前目录下所有可见文件的详细属性,包括文件属性、所有者、文件大小等信息。但是,当其显示符号链接的属性时,无论其指向文件属性如何,都会显示777,即任何人可读可写可执行。本文从ls命令源码出发,由浅入深地分析该现象的原因,简略探究了Linux 4.10下的符号链接链接、文件系统与权限的源码实现。

 

关键词:Linux ls 符号链接 文件系统 权限 源码分析

二、引言

2.1 Linux文件权限

在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。

2.2 符号链接

如前所述,若第一个字符显示为l,说明该文件是符号链接。符号链接(软链接)是一类特殊的文件, 其包含有一条以绝对路径或者相对路径的形式指向其它文件或者目录的引用[12]。符号链接的操作是透明的:对符号链接文件进行读写的程序会表现得直接对目标文件进行操作。某些需要特别处理符号链接的程序(如备份程序)可能会识别并直接对其进行操作。一个符号链接文件仅包含有一个文本字符串,其被操作系统解释为一条指向另一个文件或者目录的路径。它是一个独立文件,其存在并不依赖于目标文件。如果删除一个符号链接,它指向的目标文件不受影响。如果目标文件被移动、重命名或者删除,任何指向它的符号链接仍然存在,但是它们将会指向一个不复存在的文件。这种情况被有时被称为被遗弃。

但是,我们常常发现,创建符号链接其权限就会显示为lrwxrwxrwx,为什么?是ls命令对符号链接进行了处理,还是文件本身权限即如此?这样会不会带来一些安全隐患?怀着这些问题,本文由浅入深,从ls命令出发,探索了其背后的系统调用至vfs文件系统实现细节,力求解释这些问题。但作者水平有限,尚有很多细节不清楚,不对之处恳请批评指正。

 

 

 

 

三、ls命令分析

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的。

四、lstat系统调用

4.0 系统调用与文件系统基础

4.0.1 从C语言到系统调用

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函数了。

4.0.2 文件系统基础

为了进行后面的分析,这里需要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)。下面列出几个关键数据结构,并在关注的部分给出注释。

 

  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 */
View Code

 

4.1 user_path_at函数

有了基础补充后,继续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。后来查阅资料看是获得调用系统调用进程的数据结构信息。)

 

 

 

 

4.1.1 path_lookupat函数

接着进入path_lookupat函数,此时参数为0 | LOOKUP_RCU,即LOOKUP_ RCU。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进行一些恢复收尾工作。

4.1.2 收尾

audit_inode调用了__audit_inode,定义在kernel/auditsc.c中,保存查找的inode和device,从名字猜测是审计用,这里先不关心。然后恢复current->nameidata,并释放name内存,与开头对应。这样filename_lookup结束,返回retval变量至user_path_at函数,即返回path_lookupat的结果。由此可见user_path_at就是检查是否存在这个文件,以及相关权限是否允许。

4.2 vfs_getattr函数

4.2.1 security_inode_getattr

security_inode_getattr定义在security/security.c,首先检查dentry的inode的i_flags是否为S_PRIVATE,即inode文件系统的安装标识。若不是,则调用call_int_hook(inode_getattr, 0, path),这个宏定义就看不懂了,涉及到security_hook_heads的很多东西,这里先忽略。

4.2.2 vfs_getattr_nosec

注释说明是在没有安全检查的情况下获得属性,即没有调用security_inode_getattr,实现很简单,从dentry目录项中获得inode,然后调用inode索引节点对象的getattr方法,再用generic_fillattr填充到返回的stat中,stat->mode=inode->i_mode。Inode方法中保存的函数指针,就指向了每个具体文件操作系统的的

以上是关于Linux下ls命令显示符号链接权限为777的探索的主要内容,如果未能解决你的问题,请参考以下文章

在LINUX中如何用命令将某文件夹权限设定为777?

Linux下查看文件权限修改文件权限的方法

linux基础-ls命令

Linux文件权限符号含义

linux 下怎么查询指定目录下所有文件的权限?

在Linux中,能够显示文件类型和访问权限的命令是