Unix进程相关用户ID、用户组ID详解


我们在使用类UNIX系统时,经常会涉及到各种ID,比如,文件属性相关的用户ID、组ID,进程运行时相关的6个ID:实际ID、实际组ID、有效ID、有效组ID、保存的用户设置ID、保存的设置组ID。

实际使用过程中,我们经常搞混各个ID基本概念和使用方式,所以,本文用于记录相关内容,方便查阅和使用。

Unix文件相关属性

“一切皆文件”是Unix的基本哲学,Unix系统的所资源都可以用文件来表示。具体到每个文件,其都会有相关的文件属性,本文要说的文件的用户ID、组ID,文件的访问权限就包括在文件的属性中。

文件属性操作相关命令

  1. 通过stat命令查看文件的所有属性:

     lhl@ubuntu18:~/develops/linux$ stat test
       文件:test
       大小:0 	块:0  IO 块:4096   普通空文件
     设备:801h/2049d	Inode:5769793 硬链接:1
     权限:(0644/-rw-r--r--)  Uid:( 1000/ lhl)   Gid:( 1000/ lhl)
     最近访问:2020-02-12 12:54:38.006718573 +0800
     最近更改:2020-02-12 12:54:38.006718573 +0800
     最近改动:2020-02-12 12:54:38.006718573 +0800
     创建时间:-
    

    可以看到,文件的权限、用户ID、组ID。

  2. 通过id命令查看某个用户的相关信息,默认为当前用户。

     lhl@ubuntu18:~/develops/linux$ id root
     uid=0(root) gid=0(root) 组=0(root)
    
  3. 通过chown命令修改文件所属的用户和组。

     hl@ubuntu18:~/develops/linux$ chown --help
     用法:chown [选项]... [所有者][:[组]] 文件...
    
     -R选项:用于递归修改各级目录下的文件所属用户和用户组。
    
  4. 通过chmod修改文件相关权限。

     lhl@ubuntu18:~/develops/linux$ chmod --help
     用法:chmod [选项]... 模式[,模式]... 文件...
    
     其中,模式来自于'[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.
    
  5. 通过usermod修改用户相关的信息。

     比如,将用户lhl添加到docker用户组中。
    
     lhl@ubuntu18:~/develops/docker/docs$ sudo usermod -aG docker lhl
     [sudo] lhl 的密码: 
     lhl@ubuntu18:~/develops/docker/docs$ id lhl
     uid=1000(lhl) gid=1000(lhl) 组=1000(lhl),127(docker)
    

进程相关ID

与一个进程相关的ID有6个或更多,表示如下:

设置用户ID和设置组ID

下面分别解析一下各种ID:

  • 首先,必须明确上述个ID是依赖于进程存在的,每当谈到这些ID时,都是相对于进程来说的。

  • 实际用户ID和实际组ID标识我是谁。那么,在Unix系统中,这个“谁”是如何来的呢?答案就是,我们登陆系统时,肯定会指定登陆用户,那么这个登陆用户就是这里的“我“。用户登陆成功后,在整个登陆会话期间,这个“我”不会改变。

  • 有效用户ID和有效组ID决定了进程在访问文件时可以获得权限。

  • 保存的设置用户ID和保存的设置组ID在执行一个程序时包含了有效用户ID和有效组ID的副本。

设置用户ID和设置组ID

通常情况下,进程的有效用户ID等于实际用户ID,进程的有效组ID等于实际组ID。每个文件都有自己的所有者和组所有者,可以通过stat命令查看。

由上文可知,进程依赖于有效用户ID和有效组ID来提供对于文件的访问权限的能力。Unix文件系统提供一种这样的能力,那就是,通过在可执行文件的模式字st_mode中设置一个特殊的标志,即"设置用户ID"来实现进程运行时的有效用户ID的切换,即由实际用户ID转换为可执行文件的所有者ID。对于有效用户组,在st_mode中同样有“设置用户组ID”标志位。

举个例子,Unix的系统passwd命令,用户可以使用该命令修改指定用户的密码,该可执行程序文件的权限如下所示:

lhl@ubuntu18:/usr/bin$ stat passwd 
  文件:passwd
  大小:59640     	块:120        IO 块:4096   普通文件
设备:801h/2049d	Inode:3408705     硬链接:1
权限:(4755/-rwsr-xr-x)  Uid:(    0/    root)   Gid:(    0/    root)

注意,passwd所有者权限中的那个s,表示启用了“设置用户ID”,而且passwd的所有者ID和所有者组ID都为0(root)。

passwd最终修改/etc/passwd和/etc/shadow两个文件来达到用户密码修改目的,/etc/passwd和/ect/shadow两个文件的用户ID和用户组ID都是root。普通用户是没有权限需改这个两个文件的,但是,通过给passwd增加“设置用户ID“标志之后,就可以实现任何普通用户修改/etc/passwd和/etc/shadow文件的目的。

做一个实验,如果将passwd的“设置用户ID”标志位去掉,那么普通用户也就失去了密码的修改能力了。

  1. 去掉passwd的“设置用户ID”标志位。

     lhl@ubuntu18:/usr/bin$ sudo chmod u-s passwd 
     [sudo] lhl 的密码: 
     lhl@ubuntu18:/usr/bin$ stat passwd
       文件:passwd
       大小:59640     	块:120        IO 块:4096   普通文件
     设备:801h/2049d	Inode:3408705     硬链接:1
     权限:(0755/-rwxr-xr-x)  Uid:(    0/    root)   Gid:(    0/    root)
    

通过stat passwd可以看到“设置用户ID”标志位已经去掉了。

  1. 尝试修改用户密码。

     lhl@ubuntu18:/usr/bin$ passwd lhl
     更改 lhl 的密码。
     (当前)UNIX 密码: 
     输入新的 UNIX 密码: 
     重新输入新的 UNIX 密码: 
     passwd:认证令牌操作错误
     passwd:密码未更改
    

可以看到普通用户lhl,失去了修改其用户密码的能力。

注意,由于给可执行文件增加”设置用户ID“或”设置用户组ID”权限之后,普通用户获得了额外的权限,所以,在使用这项能力时,要特别的谨慎,否则极有可能会造成系统安全问题。

文件访问权限

上面提到过,任何文件都有访问权限,这些对于文件的访问权限保存在文件的模式字st_mode中,Unix系统为三类用户:文件用户,文件用户组,其他用户,分别提供三种权限:可读、可写、可执行,所以共9种权限。

对于文件权限的理解,应该注意一下几个方面:

  • 目录文件:Unix系统把目录当做一种文件,称为目录文件。目录文件的三种权限的含义,经常引起误解,正确的含义如下:
    • 读权限:读取当前目录下所有文件名的权限,所谓的当前目录就是指的具有相应权限的目录。
    • 写权限:修改当前目录下所有文件名的权限,所以在一个目录下创建一个新的文件,或者删除一个文件时,需要对该文件所在的目录具有写权限和执行权限。
    • 执行权限:搜索当前目录下所有文件名的权限,所谓搜索,即 进入目录的权限。对于这个权限,需要注意的PATH环境变量中命令目录,如果引用了一个没有可执行权限的目录,那么shell就不会在该目录下搜索到想要的命令。
  • 对一个文件的读权限决定了我们能够打开该文件进行读操作,这与open函数的O_RSONLY和O_RDWR标志相关。
  • 对一个文件的写权限决定了我们能够打开该文件进行写操作,这与open函数的O_WRONLY和O_RDWR标志相关。
  • 函数族exec执行任何一个普通文件时,必须要有对于该文件的可执行权限。

进程操作文件权限

进程每次打开、创建、删除文件时,内核都会匹配文件访问权限。下图对展示了进程foo访问文件file1时,内核所做的访问权限检验过程。

进程文件权限测试机制

可以看到,由于进程的有效用户ID和有效用户组ID为101,而文件的用户ID和用户组ID为100,所以,进程匹配到的文件访问权限为r-x,而进程foo调用open系统调用打开文件file1时,需要的访问权限是O_RDWR,所以,进程打开文件失败,失败原因是权限不足。

更改用户ID和组ID

Unix系统中,进程的权限依赖于文件本身的权限、进程的有效用户ID、有效组ID以及内核中进程关于文件权限的验证系统。实际情况下,进程可能为了获得对于某些资源的访问权限,需要提权;同样,为了系统安全,进程也会放弃对于某些敏感资源的访问权限,所以,需要降权。一般情况下,在设计应用程序时,我们应该本着“最小特权”的原则设计我们的应用程序,即,应用程序应该只具有完成自身任务所需的最小特权。

那么,Unix如何实现“提权”和“降权”呢?主要依赖于两个函数和一套规则,先说两个函数。

  1. 设置函数

     #include <unistd.h>
     
     int setuid(uid_t uid);
     int setguid(uid_t gid);
     			两个函数,执行成功返回0,出错返回-1,可以通过perror查看具体的错误信息。
    
  2. 规则

    • 若进程具有root权限,则setuid函数将实际用户ID、有效用户ID,以及保存的设置用户ID设置为uid;
    • 若进程没有root权限,但uid等于实际用户ID或保存的设置用户ID,则setuid只将有效用户ID设置为uid,不改变实际用户ID和保存的设置用户ID;
    • 若上面两个条件都不满足,则将errno设置为EPERM,并返回-1。
  3. 注意事项

    • 只有root用户可以修改进程的实际用户ID;
    • 仅当进程文件设置了设置用户ID标志位时,exec函数才会设置有效用户ID,并且将有效用户ID设置为文件的用户ID。否则,exec不会改变有效用户ID,而是将其维持原值。任何时候,都可以调用setuid,将有效用户ID设置为实际用户ID和保存的设置用户ID。自然,有效用户ID不能任意设置值;
      修改进程的实际用户ID;
    • 保存的设置用户ID是由exec复制有效用户ID而来的。若设置了进程文件的设置用户ID位,则在执行exec时,其会将文件的用户ID设置为保存的设置用户ID,然后保存这个副本。
发布了121 篇原创文章 · 获赞 130 · 访问量 45万+

猜你喜欢

转载自blog.csdn.net/linux_embedded/article/details/104288134