Linux权限模型详解

0 引言

本文主要总结linux权限模型相关的原理,譬如

  • ruid,euid,rgid,egid的含义
  • setuid,seteuid等系统调用的作用及使用讲解
  • 如何获得linux相应进程的权限

等各种问题。

1 unix权限模型

文件,pipe,内存等这些对象都是以某种方式共享的,因此它们需要某种保护机制来保护它们不被滥用;这种机制称为Unix权限模型。

譬如,你有一个文件myfile,你不想让别人去写该文件,那么你便可以通过unix权限模型对文件进行相应的权限设置。

unix权限模型将资源的权限分为三组:

U(所有者, Owner), G(用户组, Group), O(其他, Other),而每组又由三个权限位构成,分别为r(读权限), w(写权限), x(可执行权限).

譬如,在我机器上运行如下命令

qls@qls-VirtualBox:~/cpp_learn$ ls -l main
-rwxrwxr-x 1 qls qls 155488 Jun 10 15:53 main

则结合上述可知,其具体可汇总如下表所示

访问类别

Owner(U)

Group(G)

Others(O)

权限位

r

w

x

r

w

x

r

w

x

例子(main)

1

1

1

1

1

1

1

0

1

通过上表可知文件的所有者拥有可读,可写,可执行权限。而其他用户只拥有可读可执行权限。

在linux中,可通过chmod命令来更改文件的权限位,同时所谓的777,065等也即是上述权限位的不同组合。

2 Unix权限模型工作原理

Unix权限模型的工作需要涉及 共享对象(譬如上述main文件)以及访问该对象的进程。

首先,每个用户有一个记录(record), 该record记录在/etc/passwd 文件中,譬如我的虚拟机的用户qls,因此可得出如下结果

grep qls /etc/passwd
qls:x:1000:1000:qls,,,:/home/qls:/bin/bash

由上述可知,每个record由7列构成,其相应的内容如下

username::UID:GID:descriptive_name:home_dir:program

本文仅解释上述UID,GID以及program列的含义,

UID(也即用户标识符),GID(用户组标识符),譬如我在虚拟机的一个用户为qls,其UID为1000,GID为1000

program指的是用户登陆成功后所运行的程序,这列通常是shell,但也可以设置成任何其他的东西。

通过id命令可以查看用户的UID和GID,譬如在我的虚拟机运行id命令,可得如下结果

qls@qls-VirtualBox:~/cpp_learn$ id
uid=1000(qls) gid=1000(qls) groups=1000(qls),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare)

问题1: 一个进程的uid和gid是如何得来的呢?

进程的uid继承至登陆的shell进程,也即每个用户的进程的uid和gid为用户的uid和gid。

问题2: OS是如何检查某个进程是否有权限操作某个资源呢?

  • 检查以及确认资源(文件)的所有权和组所有权
  • 检查进程目前具有哪个访问类别(譬如,U|G|O)
  • 针对相应的访问类别,相应的权限位是否设置(譬如 w设置为1,则允许写文件)

问题3:文件的所有权和组所有权如何获得?

文件的所有权和组所有权可以从文件的inode获取,两者均存放在inode数据结构中。在用户侧可通过stat系统调用获得inode的详细信息。

问题4:如何确定进程的访问类别?

在Unix权限模型中,访问类别主要由U(所有者,Owner), G(Group, 组),O(Other),一个进程的访问类别由OS通过如下方式确定

   if process_UID == file_UID
   then
        access_category = U
   else if process_GID == file_GID
   then
        access_category = G
   else
        access_category = O
   fi

上述需要一些说明,一个进程会存在属于多个组,拥有多个GID, 所以只要有一个GID与文件的GID相等,那么进程的访问类别便为G

当确定了进程的访问类别后,便可根据其权限位进行相应的权限判断。

3 RUID,RGID,EUID以及EGID讲解

为了简化,此处引入一个进程证书,其主要由{RUID,RGID,EUID,EGID}标识,当然,其也可以包含更多的属性,譬如PPID,PID等。

一个进程的UID主要由两个整数构成

  • RUID(真实用户ID)
  • EUID (有效用户ID)

一个进程的GID主要由两个整数构成

  • RGID (真实用户组ID)
  • EGID   (有效用户组ID)

所谓真实用户ID(RUID), 也即进程所属的用户的ID,所谓真实用户组ID(RGID),也即进程所属的用户的组id,其具体值为/etc/passwad文件中相应record中相应的UID和GID的值。

有效用户ID是针对OS而言的,操作系统根据有效用户ID和有效用户组ID来判断相应进程的运行权限,具体来说

  • 当对进程执行相应的权限检查时,OS是根据EUID/EGID来判断,而不是根据RUID/RGID
  • EUID=0时,操作系统认为进程具有根权限

默认场景下:EUID = RUID, EGID=RGID

为了进一步加深对RUID,EUID等的理解,此处需要从一个问题出发也即

一个非特权用户如何更改其密码?

3.1 setuid二进制执行文件

为了回答上述问题,此处单开一小节。

在linux系统中,可知密码存在于/etc/passwd文件中,但为了安全性考虑,一般会存在/etc/shadow文件中。

qls@qls-VirtualBox:~/cpp_learn$ ls -l /etc/shadow
-rw-r----- 1 root shadow 1459 Sep 27  2021 /etc/shadow

由上述可知,/etc/shadow文件的真实用户为root,且只有root有可写可读权限。

在linux中,我们可以通过paswad命令来修改用户密码,但此刻出现了一个问题,

/etc/shadow只有root有读写权限,而普通用户没有读写权限,那么paswad为什么可以更改用户密码呢?

具体来看一下passwd的权限属性,在我的开发机如下所示

 由上述可知,在U访问类别中,权限位的可执行位不是x而是一个s。

s权限位是一个特殊的标志,每当一个可执行文件的U类别的权限位出现s,那么这个可执行文件便称作setuid二进制文件。

每当一个setuid二进制文件运行时,其相应的进程的EUID便被设置为二进制文件的UID,针对passwd而言,进程的EUID被设置为0,也即使进程具备根权限。故其可以使普通用户更改相应的密码。

可以通过chown和chmod更改文件的的权限位并设置权限位为s。

4 系统调用(setuid,setgid,getuid,getgid等)

本部分为本文总结的最后一部分。该部分主要介绍如下几个系统调用

   #include 
   #include 
   uid_t getuid(void);
   uid_t geteuid(void);
   gid_t getgid(void);
   gid_t getegid(void);

上述四个系统调用返回进程的标识{UID,EUID,GID,EGID}, 下面以一个示例代码来讲解上述系统调用

#include 

#include 
#include 

int main() {
  std::cout << "UID= " << getuid() << "\n"
            << "EUID= " << geteuid() << "\n"
            << "GID= " << getgid() << "\n"
            << "EGID= " << getegid() << "\n";

  if (geteuid() == 0) {
    std::cout << "running as root\n";
    sleep(1);
  }

  return 0;
}

通过运行相应的二进制文件,获得如下结果

Linux权限模型详解_第1张图片

 当通过sudo运行相应的二进制文件时,获得如下结果

Linux权限模型详解_第2张图片

 那么sudo是如何工作的呢?关于该问题,相应的答案本文不再赘述。

4.1 设置进程证书

本部分主要讲解如下几个系统调用

   #include 
   #include 
   int setuid(uid_t uid);
   int setgid(gid_t gid);
   int seteuid(uid_t euid);
   int setegid(gid_t egid);
   int setreuid(uid_t ruid, uid_t euid);
   int setregid(gid_t rgid, gid_t egid);

需要说明的是

  • seteuid/setuid系统调用在root权限下可以将EUID和RUID设置为参数euid和uid
  • 一个非root用户只能将EUID设置为其RUID

4.2 放弃特权

本部分主要以一个场景讲解,具体如下

如果业务中需要某个程序首先在特权状态下操作,譬如读取某些root用户的文件,然后需要切换到非特权状态。

为了达到此目的,可以利用setuid root binary相关的操作。

具体来说,你可以利用setuid(getuid())来达到相应的目的。

具体的示例代码本文后续补上。

5 总结

本文具体讲解了linux权限模型相关的概念以及EUID和RUID等具体的使用。通过本文可以加深对相应的概念的理解。

你可能感兴趣的:(linux,linux,运维,服务器)