分类 Ubuntu 下的文章

“Ubuntu是以桌面应用为主的Linux发行版,基于Debian。Ubuntu有三个正式版本,包括桌面版、服务器版及用于物联网设备和机器人的Core版。从17.10版本开始,Ubuntu以GNOME为默认桌面环境。 Ubuntu是著名的Linux发行版之一,也是目前最多用户的Linux版本。 ”

extern void GetAllFlags(std::vector<CommandLineFlagInfo>* OUTPUT)接口

示例代码:

#include "bits/stdc++.h"
#include <gflags/gflags.h>
#include "flags.h"

using namespace std;

DEFINE_int32(portno, 0, "portno,默认为0");

int main(int argc, char* argv[])
{
    google::SetVersionString("V1.0.0@const.net.cn");
    google::SetUsageMessage("-help ");
    google::ParseCommandLineFlags(&argc, &argv, true);

    std::vector<google::CommandLineFlagInfo> OUTPUT;
    GetAllFlags(&OUTPUT);
    for(auto i=0;i<OUTPUT.size();i++)
    {
        cout<<OUTPUT[i].name<<"="<<OUTPUT[i].current_value<<endl;
    }
    return 0;
}

 ./test_gflags -portno=11

flagfile=
fromenv=
tryfromenv=
undefok=
tab_completion_columns=80
tab_completion_word=
help=false
helpfull=false
helpmatch=
helpon=
helppackage=false
helpshort=false
helpxml=false
version=false
bool_var=true
double_var=0
int32_var=0
int64_var=0
string_var=
uint32_var=0
uint64_var=0
portno=11

apt-get install lm-sensors

sensors coretemp-isa-0000

coretemp-isa-0000
Adapter: ISA adapter
Package id 0: +41.0°C (high = +100.0°C, crit = +100.0°C)
Core 0: +39.0°C (high = +100.0°C, crit = +100.0°C)
Core 1: +39.0°C (high = +100.0°C, crit = +100.0°C)
Core 2: +39.0°C (high = +100.0°C, crit = +100.0°C)
Core 3: +38.0°C (high = +100.0°C, crit = +100.0°C)
Core 4: +40.0°C (high = +100.0°C, crit = +100.0°C)
Core 5: +39.0°C (high = +100.0°C, crit = +100.0°C)

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
mmap优点共有一下几点:
1、对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。
2、实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。
3、提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。
     同时,如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。
4、可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候,mmap都可以发挥其功效。
mmap使用细节
1、使用mmap需要注意的一个关键点是,mmap映射区域大小必须是物理页大小(page_size)的整倍数(32位系统中通常是4k字节)。原因是,内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页。
2、内核可以跟踪被内存映射的底层对象(文件)的大小,进程可以合法的访问在当前文件大小以内又在内存映射区以内的那些字节。也就是说,如果文件的大小一直在扩张,只要在映射区域范围内的数据,进程都可以合法得到,这和映射建立时文件的大小无关。
3、映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关。同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。
Linux可以在用户态下,通过mmap函数将设备节点/dev/mem进行映射,实现在用户态下将物理地址映射到虚拟地址,并通过对虚拟地址的修改来实现寄存器的修改。
/dev/mem是Linux系统下的物理内存的全镜像,可通过该节点实现对物理内存的访问。
一般用于在嵌入式中以用户态形式直接访问寄存器/物理IO设备等。
通常用法是open这个设备节点文件,然后mmap进行内存映射,就可以使用map之后的地址访问物理内存。
 linux下的mmap函数和munmap函数

mmap函数可以将一个文件或者其它对象映射进内存。
这里我们使用mmap函数将设备节点/dev/mem映射到内存中。
在操作结束后,需要使用munmap函数反映射。
函数头文件:

#include <sys/mman.h>

mmap函数原型:
void mmap(void start,size_t length,int prot,int flags,int fd,off_t offset);
参数说明如下:
start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。需要按照页面大小对齐,否则会出错。
length:映射区的长度。长度单位是以字节为单位,不足一内存页按一内存页处理
prot:期望的内存保护标志,类似于可读可写等
flags:指定映射对象的类型
fd:文件标识符
offset:被映射对象内容的起点,需要按照页面大小对齐,否则会出错。
返回值:成功执行时,mmap()返回被映射区的指针,失败时,mmap()返回MAP_FAILED[其值为(void *)-1],错误原因会被errno记录。
注:获取系统页面大小的方式:

#include <unistd.h>
long page_size = sysconf(_SC_PAGESIZE);

munmap函数
munmap函数是mmap函数的反过程,取消对某地址的映射。
函数原型:
int munmap(void* start,size_t length);
参数:
start:虚拟内存起始地址
length:映射长度
返回值:
成功返回0,失败返回-1。错误原因也会被errno记录。

实现对物理地址的读取和写入,方便在用户态调试寄存器的实例代码

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#define ARGV_ADDR_POS 2
#define ARGV_DATA_POS 3
#define ARGV_WR_OFF_POS 3
#define ARGV_WR_DATA_POS 4
#define ARGV_CMD_POS 1
//显示使用方法
void printUsage(char* argv[])
{
    printf("Usage: \r\n Read address as Byte: %s r [ADDR] [LEN] \r\n Read address as int: %s i [ADDR] [LEN] \r\n", argv[0], argv[0]);
    printf(" Write to address: %s w [ADDR] [OFF] [DATA]\r\n", argv[0]);
}
//读取某个基地址addr+byte字节的数据并逐个字节显示。
//从DataSheet上看,页大小为0x400,因此这里的基地址addr必须是能够被0x400整除,否则Segment Error.
void readMMap(int dev_fd,unsigned int addr,unsigned int byte)
{
    int mmapByte = (byte/4*4+4),iloop;
    int realByte = (byte/4*4);
    //这里的mmapByte计算多此一举,因为这里只要进行映射都会按照页面大小向上取整,一般不用担心超出地址的问题
    unsigned char *map_base = (unsigned char * )mmap(NULL, mmapByte, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, addr );  //使用MMAP直接映射基地址的内存数据到map_base
    int i,j;
    if(map_base == (unsigned char *)-1)
    {
        printf("MMAP Error.\r\n");
        return;
    }
    printf("           | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n");
    printf("------------------------------------------------------------\r\n");
    iloop = realByte/16+((realByte%16)?1:0);
    //printf("iloop is %d\r\n",iloop);
    for(i = 0;i < iloop;i++)
    {
        int loopbyte = (byte-i*16 > 16)?16:(byte-i*16);
        //printf("loopbyte = %d\n",loopbyte);ARGV_WR_OFF_POS
        printf("0x%08X | ",addr+i*16);
        for(j = 0;j < loopbyte;j++)
        {
            printf("%02X ",*(volatile unsigned char *)(map_base+i*16+j));
        }
        printf("\r\n");
    }
    munmap(map_base,mmapByte);
}
//读取某个基地址addr+byte字节的数据并按照逐个int显示。
//从DataSheet上看,页大小为0x400,因此这里的基地址addr必须是能够被0x400整除,否则Segment Error.
void readMMapByUINT(int dev_fd,unsigned int addr,unsigned int byte)
{
    int mmapByte = (byte/4*4+4),iloop;
    int realByte = (byte/4*4);
    unsigned char *map_base = (unsigned char * )mmap(NULL, mmapByte, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, addr );
    unsigned int *map_base_i =  (unsigned int * )map_base;
    int i,j;
    if(map_base == (unsigned char *)-1)
    {
        printf("MMAP Error.\r\n");
        return;
    }
    printf("           | +0x00      +0x04      +0x08      +0x0C         \r\n");
    printf("------------------------------------------------------------\r\n");
    iloop = realByte/16+((realByte%16 > 0)?1:0);
    for(i = 0;i < iloop;i++)
    {
        int loopcnt = (byte-i*4 > 4)?4:byte-i*4;
        printf("0x%08X | ",addr+i*16);
        for(j = 0;j < loopcnt;j++)
        {
            printf("0x%08X ",*(volatile unsigned int *)(map_base_i+i*4+j));
        }
        printf("\r\n");
    }
    munmap(map_base,mmapByte);
}
//将数据data写入基地址addr+offset的位置。
void writeMMap(int dev_fd,unsigned int addr,unsigned int offset,unsigned int data)
{
    unsigned int mmapByte = offset + 0x04;
    unsigned char *map_base = (unsigned char * )mmap(NULL, mmapByte, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, addr );  
    if(map_base == (unsigned char *)-1)
    {
        printf("MMAP Error.\r\n");
        return;
    }
    printf("Before Modify : addr 0x%08X = 0x%08X\r\n",(addr+offset),*(volatile unsigned int *)(map_base+offset));
    *(volatile unsigned int *)(map_base + offset) = data;
    printf("After Modify : addr 0x%08X = 0x%08X\r\n",(addr+offset),*(volatile unsigned int *)(map_base+offset));
    munmap(map_base,mmapByte);
}
int main(int argc,char* argv[])
{
    int addr = 0,byte = 0,fd = 0,off = 0;
    if(argc < 4)
    {
        printf("ARG ERROR.\r\n");
        printUsage(argv);
        return 0;
    }
    addr = strtoul(argv[ARGV_ADDR_POS],0,0);
    byte = strtoul(argv[ARGV_DATA_POS],0,0);
    
    if(addr == 0)
    {
        printf("Addr Err.\r\n");
        return 0;
    }
    fd = open("/dev/mem", O_RDWR | O_NDELAY);
    if (fd < 0)  
    {
        printf("open(/dev/mem) failed.");    
        return 0;
    }
    switch(argv[ARGV_CMD_POS][0])
    {
        case 'r':
            if(byte == 0)
            {
                printf("Byte len Err.\r\n");
                close(fd);
                return 0;
            }
            printf("Now Read Memory at 0x%08X by %d\r\n",addr,byte);
            readMMap(fd,addr,byte);
            break;
        case 'i':
            if(byte == 0)
            {
                printf("Byte len Err.\r\n");
                close(fd);
                return 0;
            }
            printf("Now Read Memory By int at 0x%08X by %d\r\n",addr,byte);
            readMMapByUINT(fd,addr,byte);
            break;
        case 'w':
            if(argc < 5)
            {
                printf("Write ARGC Error.");
                close(fd);
                return 0;
            }
            off = strtoul(argv[ARGV_WR_OFF_POS],0,0);
            byte = strtoul(argv[ARGV_WR_DATA_POS],0,0);
            printf("Now Write Memory By int at 0x%08X + 0x%08X by 0x%08X\r\n",addr,off,byte);
            writeMMap(fd,addr,off,byte);
            break;
        default:
            printf("Error Cmd.\r\n");
            break;
    }
    close(fd);
    return 0;
}

编译

gcc mmap1.c -o mmap_test

运行

./mmap_test 

ARG ERROR.
Usage:
Read address as Byte: ./mmap_test r [ADDR] [LEN]
Read address as int: ./mmap_test i [ADDR] [LEN]
Write to address: ./mmap_test w [ADDR] [OFF] [DATA]

sudo ./a.out r 0x6a600000 32

Now Read Memory at 0x6A600000 by 32

       | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

0x6A600000 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x6A600010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

sudo ./a.out r 0x6a600000 32 

Now Read Memory at 0x6A600000 by 32

       | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

0x6A600000 | 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x6A600010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

sudo ./a.out w 0x6a600000 1 3

Now Write Memory By int at 0x6A600000 + 0x00000001 by 0x00000003
Before Modify : addr 0x6A600001 = 0x00000002
After Modify : addr 0x6A600001 = 0x00000003

sudo ./a.out r 0x6a600000 32 

Now Read Memory at 0x6A600000 by 32

       | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

0x6A600000 | 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x6A600010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Ubuntu 20使用rc-local时,出现这个错误提示。
The unit files have no [Install] section. They are not meant to be enabled using systemctl.

解决办法:
rc-local.service添加Install字段。

[Install]
WantedBy=multi-user.target

结果:

cat /lib/systemd/system/rc-local.service
[Unit]
Description=/etc/rc.local Compatibility
Documentation=man:systemd-rc-local-generator(8)
ConditionFileIsExecutable=/etc/rc.local
After=network.target

[Service]
Type=forking
ExecStart=/etc/rc.local start
TimeoutSec=0
RemainAfterExit=yes
GuessMainPID=no

[Install]
WantedBy=multi-user.target

重新enable
systemctl enable rc-local.service

创建/etc/rc.local,并赋予执行权限

cat /etc/rc.local
#!/bin/sh
echo "ok" >> ~/rc.local.ok
exit 0

Linux的capability深入分析     

这个也是转载页面,原始页面没找到,谢谢原作者!

一)概述:
1)从2.1版开始,Linux内核有了能力(capability)的概念,即它打破了UNIX/LINUX操作系统中超级用户/普通用户的概念,由普通用户也可以做只有超级用户可以完成的工作.

Capabilities的主要思想在于分割root用户的特权,即将root的特权分割成不同的能力,每种能力代表一定的特权操作。例如:能力CAP_SYS_MODULE表示用户能够加载(或卸载)内核模块的特权操作,而CAP_SETUID表示用户能够修改进程用户身份的特权操作。在Capbilities中系统将根据进程拥有的能力来进行特权操作的访问控制。
    在Capilities中,只有进程和可执行文件才具有能力,每个进程拥有三组能力集,分别称为cap_effective, cap_inheritable, cap_permitted(分别简记为:pE,pI,pP),其中cap_permitted表示进程所拥有的最大能力集;cap_effective表示进程当前可用的能力集,可以看做是cap_permitted的一个子集;而cap_inheitable则表示进程可以传递给其子进程的能力集。系统根据进程的cap_effective能力集进行访问控制,cap_effective为cap_permitted的子集,进程可以通过取消cap_effective中的某些能力来放弃进程的一些特权。可执行文件也拥有三组能力集,对应于进程的三组能力集,分别称为cap_effective, cap_allowed 和 cap_forced(分别简记为fE,fI,fP),其中,cap_allowed表示程序运行时可从原进程的cap_inheritable中集成的能力集,cap_forced表示运行文件时必须拥有才能完成其服务的能力集;而cap_effective则表示文件开始运行时可以使用的能力。

各种能力:
CAP_CHOWN:修改文件属主的权限
CAP_DAC_OVERRIDE:忽略文件的DAC访问限制
CAP_DAC_READ_SEARCH:忽略文件读及目录搜索的DAC访问限制
CAP_FOWNER:忽略文件属主ID必须和进程用户ID相匹配的限制
CAP_FSETID:允许设置文件的setuid位
CAP_KILL:允许对不属于自己的进程发送信号
CAP_SETGID:允许改变进程的组ID
CAP_SETUID:允许改变进程的用户ID
CAP_SETPCAP:允许向其他进程转移能力以及删除其他进程的能力
CAP_LINUX_IMMUTABLE:允许修改文件的IMMUTABLE和APPEND属性标志
CAP_NET_BIND_SERVICE:允许绑定到小于1024的端口
CAP_NET_BROADCAST:允许网络广播和多播访问
CAP_NET_ADMIN:允许执行网络管理任务
CAP_NET_RAW:允许使用原始套接字
CAP_IPC_LOCK:允许锁定共享内存片段
CAP_IPC_OWNER:忽略IPC所有权检查
CAP_SYS_MODULE:允许插入和删除内核模块
CAP_SYS_RAWIO:允许直接访问/devport,/dev/mem,/dev/kmem及原始块设备
CAP_SYS_CHROOT:允许使用chroot()系统调用
CAP_SYS_PTRACE:允许跟踪任何进程
CAP_SYS_PACCT:允许执行进程的BSD式审计
CAP_SYS_ADMIN:允许执行系统管理任务,如加载或卸载文件系统、设置磁盘配额等
CAP_SYS_BOOT:允许重新启动系统
CAP_SYS_NICE:允许提升优先级及设置其他进程的优先级
CAP_SYS_RESOURCE:忽略资源限制
CAP_SYS_TIME:允许改变系统时钟
CAP_SYS_TTY_CONFIG:允许配置TTY设备
CAP_MKNOD:允许使用mknod()系统调用
CAP_LEASE:允许修改文件锁的FL_LEASE标志

cap_chown=eip是将chown的能力以cap_effective(e),cap_inheritable(i),cap_permitted(p)三种位图的方式授权给相关的程序文件.

2)capability可以作用在进程上(受限),也可以作用在程序文件上,它与sudo不同,sudo只针对用户/程序/文件的概述,即sudo可以配置某个用户可以执行某个命令,可以更改某个文件,而capability是让某个程序拥有某种能力,例如:

capability让/tmp/testkill程序可以kill掉其它进程,但它不能mount设备节点到目录,也不能重启系统,因为我们只指定了它kill的能力,即使程序有问题也不会超出能力范围.

3)每个进程有三个和能力有关的位图:inheritable(I),permitted(P)和effective(E),对应进程描述符task_struct(include/linux/sched.h)里面的cap_effective, cap_inheritable, cap_permitted,所以我们可以查看/proc/PID/status来查看进程的能力.

4)cap_effective:当一个进程要进行某个特权操作时,操作系统会检查cap_effective的对应位是否有效,而不再是检查进程的有效UID是否为0.

例如,如果一个进程要设置系统的时钟,Linux的内核就会检查cap_effective的CAP_SYS_TIME位(第25位)是否有效.

5)cap_permitted:表示进程能够使用的能力,在cap_permitted中可以包含cap_effective中没有的能力,这些能力是被进程自己临时放弃的,也可以说cap_effective是cap_permitted的一个子集.

6)cap_inheritable:表示能够被当前进程执行的程序继承的能力.

二)capability的设定与清除
我们在下面的程序中给当前的进程设定能力,最后我们清除掉所设定的能力,源程序如下:

 

1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <sys/types.h>
 5 #include <unistd.h>
 6  
 7 #undef _POSIX_SOURCE
 8 #include <sys/capability.h>
 9  
10 extern int errno;
11   
12 void whoami(void)
13 {
14   printf("uid=%i  euid=%i  gid=%i\n", getuid(), geteuid(), getgid());
15 }
16  
17 void listCaps()
18 {
19   cap_t caps = cap_get_proc();
20   ssize_t y = 0;
21   printf("The process %d was give capabilities %s\n",
22          (int) getpid(), cap_to_text(caps, &y));
23   fflush(0);
24   cap_free(caps);
25 }
26   
27 int main(int argc, char **argv)
28 {
29   int stat;
30   whoami();
31   stat = setuid(geteuid());
32   pid_t parentPid = getpid();
33  
34   if(!parentPid)
35     return 1;
36   cap_t caps = cap_init();
37  
38  
39   cap_value_t capList[5] =
40   { CAP_NET_RAW, CAP_NET_BIND_SERVICE , CAP_SETUID, CAP_SETGID,CAP_SETPCAP } ;
41   unsigned num_caps = 5;
42   cap_set_flag(caps, CAP_EFFECTIVE, num_caps, capList, CAP_SET);
43   cap_set_flag(caps, CAP_INHERITABLE, num_caps, capList, CAP_SET);
44   cap_set_flag(caps, CAP_PERMITTED, num_caps, capList, CAP_SET);
45  
46   if (cap_set_proc(caps)) {
47     perror("capset()");
48  
49     return EXIT_FAILURE;
50   }
51   listCaps();
52 
53   printf("dropping caps\n");
54   cap_clear(caps);  // resetting caps storage
55  
56   if (cap_set_proc(caps)) {
57     perror("capset()");
58     return EXIT_FAILURE;
59   }
60   listCaps();
61 
62   cap_free(caps);
63   return 0;
64 
65 }

编译:

gcc capsettest.c -o capsettest -lcap

运行:

./capsettest 

uid=0  euid=0  gid=0

The process 2383 was give capabilities = cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw+eip

dropping caps

The process 2383 was give capabilities =

注:

1)我们对该进程增加了5种能力,随后又清除了所有能力.

2)首先通过cap_init()初始化存放cap能力值的状态,随后通过cap_set_flag函数的调用,将三种位图的能力设置给了变量caps,再通过cap_set_proc(caps)设定当前进程的能力值,通过cap_get_proc()返回当前进程的能力值,最后通过cap_free(caps)释放能力值.

3)cap_set_flag函数的原型是:

int cap_set_flag(cap_t cap_p, cap_flag_t flag, int ncap,const cap_value_t *caps, cap_flag_value_t value);

我们这里的调用语句是:cap_set_flag(caps, CAP_PERMITTED, num_caps, capList, CAP_SET);

第一个参数cap_p是存放能力值的变量,是被设定值.这里是caps.

第二个参数flag是是三种能力位图,这里是CAP_PERMITTED.

第三个参数ncap是要设定能力的个数,这里是num_caps,也就是5.

第四个参数*caps是要设定的能力值,这里是capList数组,也就是CAP_NET_RAW, CAP_NET_BIND_SERVICE , CAP_SETUID, CAP_SETGID,CAP_SETPCAP.

第五个参数value是决定要设定还是清除,这里是CAP_SET.

4)cap_set_proc函数的原型是:int cap_set_proc(cap_t cap_p);

cap_set_proc函数通过cap_p中的能力值设定给当前的进程.

5)cap_get_proc函数的原型是:cap_t cap_get_proc(void);

cap_get_proc函数返回当前进程的能力值给cap变量.

6)cap_free函数的原型是:cap_free(caps);

cap_free函数清理/释放cap变量.

7)如果我们fork()了子进程,那么子进程继承父进程的所有能力.

8)不能单独设定CAP_EFFECTIVE,CAP_INHERITABLE位图,必须要和CAP_PERMITTED联用,且CAP_PERMITTED一定要是其它两个位图的超集.

9)如果两次调用cap_set_proc函数,第二次调用的值力值不能少于或多于第一次调用.如第一次我们授权chown,setuid能力,第二次只能是chown,setuid不能是其它的能力值.

10)普通用户不能给进程设定能力.

三)进程的能力掩码:
我们可以通过下面的程序获取当前进程的掩码,它是通过capget函数来获取指定进程的能力掩码,当然我们也可以用capset来设定掩码,下面获取掩码的体现:

 

1 #undef _POSIX_SOURCE
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <sys/types.h>
 5 #include <unistd.h>
 6 #include <linux/capability.h>
 7 #include <errno.h>
 8 
 9 int main()
10 {
11      struct __user_cap_header_struct cap_header_data;
12      cap_user_header_t cap_header = &cap_header_data;
13 
14      struct __user_cap_data_struct cap_data_data;
15      cap_user_data_t cap_data = &cap_data_data;
16 
17      cap_header->pid = getpid();
18      cap_header->version = _LINUX_CAPABILITY_VERSION_1;
19 
20      if (capget(cap_header, cap_data) < 0) {
21          perror("Failed capget");
22          exit(1);
23      }
24      printf("Cap data 0x%x, 0x%x, 0x%x\n", cap_data->effective,
25          cap_data->permitted, cap_data->inheritable);
26 }

gcc capget0.c -o capget0 -lcap

普通用户:

./capget0 

Cap data 0x0, 0x0, 0x0

超级用户:

/home/test/capget0 

Cap data 0xffffffff, 0xffffffff, 0x0

这也说明了默认情况下,root运行的进程是什么权限都有,而普通用户则什么权限都没有.

我们可以将本程序与上面的程序进行整合,如下:

 

1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <sys/types.h>
 5 #include <unistd.h>
 6  
 7 #undef _POSIX_SOURCE
 8 #include <sys/capability.h>
 9  
10 extern int errno;
11   
12 void whoami(void)
13 {
14   printf("uid=%i  euid=%i  gid=%i\n", getuid(), geteuid(), getgid());
15 }
16  
17 void listCaps()
18 {
19   cap_t caps = cap_get_proc();
20   ssize_t y = 0;
21   printf("The process %d was give capabilities %s\n",
22          (int) getpid(), cap_to_text(caps, &y));
23   fflush(0);
24   cap_free(caps);
25 }
26  
27  
28 int main(int argc, char **argv)
29 {
30   int stat;
31   whoami();
32   stat = setuid(geteuid());
33   pid_t parentPid = getpid();
34  
35   if(!parentPid)
36     return 1;
37   cap_t caps = cap_init();
38  
39   cap_value_t capList[5] =
40   { CAP_NET_RAW, CAP_NET_BIND_SERVICE , CAP_SETUID, CAP_SETGID,CAP_SETPCAP } ;
41   unsigned num_caps = 5;
42   cap_set_flag(caps, CAP_EFFECTIVE, num_caps, capList, CAP_SET);
43   cap_set_flag(caps, CAP_INHERITABLE, num_caps, capList, CAP_SET);
44   cap_set_flag(caps, CAP_PERMITTED, num_caps, capList, CAP_SET);
45  
46   if (cap_set_proc(caps)) {
47     perror("capset()");
48  
49     return EXIT_FAILURE;
50   }
51   listCaps();
52 
53   cap_free(caps);
54 
55   struct __user_cap_header_struct cap_header_data;
56   cap_user_header_t cap_header = &cap_header_data;
57 
58   struct __user_cap_data_struct cap_data_data;
59   cap_user_data_t cap_data = &cap_data_data;
60 
61   cap_header->pid = getpid();
62   cap_header->version = _LINUX_CAPABILITY_VERSION_1;
63 
64   if (capget(cap_header, cap_data) < 0) {
65       perror("Failed capget");
66       exit(1);
67   }
68   printf("Cap data 0x%x, 0x%x, 0x%x\n", cap_data->effective,
69   cap_data->permitted, cap_data->inheritable);
70 
71   sleep(60);
72   return 0;
73 }

 

编译并执行:

gcc capsettest.c -o capsettest -lcap

./capsettest

uid=0  euid=0  gid=0

The process 3101 was give capabilities = cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw+eip

Cap data 0x25c0, 0x25c0, 0x25c0

注:0x25c0=10 0101 1100 0000(二进制)

对映的能力如下:

cap_setgid=6(位)

cap_setuid=7(位)

cap_setpcap=8(位)

cap_net_bind_service=10(位)

cap_net_raw=13(位)

在程序sleep的时候,我们查看一下进程的status,如下:

cat /proc/pgrep capsettest/status

CapInh: 00000000000025c0

CapPrm: 00000000000025c0

CapEff: 00000000000025c0

CapBnd: ffffffffffffffff

我们看到进程的status也反映了它的能力状态.

CapBnd是系统的边界能力,我们无法改变它. 

四)capability的工具介绍
在我们的试验环境是RHEL6,libcap-2.16软件包中包含了相关的capability设置及查看工作,如下:

rpm -ql libcap-2.16-5.2.el6.i686 

/lib/libcap.so.2

/lib/libcap.so.2.16

/lib/security/pam_cap.so

/usr/sbin/capsh

/usr/sbin/getcap

/usr/sbin/getpcaps

/usr/sbin/setcap

/usr/share/doc/libcap-2.16

/usr/share/doc/libcap-2.16/License

/usr/share/doc/libcap-2.16/capability.notes

/usr/share/man/man8/getcap.8.gz

/usr/share/man/man8/setcap.8.gz

getcap可以获得程序文件所具有的能力(CAP).

getpcaps可以获得进程所具有的能力(CAP).

setcap可以设置程序文件的能力(CAP).

我们下面主要用setcap来进行调试.

五)CAP_CHOWN 0(允许改变文件的所有权)
授权普通用户可以用/bin/chown程序更改任意文件的owner,如下:

setcap cap_chown=eip /bin/chown 

查看/bin/chown程序的能力值,如下:

getcap /bin/chown               

/bin/chown = cap_chown+eip

切换到test用户,将/bin/ls程序的owner改为test,如下:

su - test

chown test.test /bin/ls

ls -l /bin/ls

-rwxr-xr-x. 1 test test 118736 Jun 14  2010 /bin/ls

注:

1)cap_chown=eip是将chown的能力以cap_effective(e),cap_inheritable(i),cap_permitted(p)三种位图的方式授权给相关的程序文件.

2)如果改变文件名,则能力保留到新文件.

3)用setcap -r /bin/chown可以删除掉文件的能力.

4)重新用setcap授权将覆盖之前的能力. 

六)CAP_DAC_OVERRIDE 1(忽略对文件的所有DAC访问限制)
授权普通用户可以用/usr/bin/vim程序修改所有文件的内容,如下:

setcap cap_dac_override=eip /usr/bin/vim 

切换到普通用户

su - test

修改/etc/shadow文件内容

vim /etc/shadow

root:663hJf.BoIVU/cdLKb$JxLXcQScrLS032aFPAQvVc4RzKYNadcIIzxmzAIw.jejrYOHhqdr0oV7sNBL.IhGBo.mMOYEdevlnCp2OGku8.:15094:0:99999:7:::

bin:*:14790:0:99999:7:::

daemon:*:14790:0:99999:7:::

adm:*:14790:0:99999:7:::

注:

DAC_OVERRIDE能力是DAC_READ_SEARCH能力的超集. 

七)CAP_DAC_READ_SEARCH 2(忽略所有对读、搜索操作的限制)
授权普通用户可以用/bin/cat程序查看所有文件的内容,如下:

setcap cap_dac_read_search=eip /bin/cat

切换到普通用户

su - test

查看/etc/shadow,如下:

cat /etc/shadow

root:663hJf.BoIVU/cdLKb$JxLXcQScrLS032aFPAQvVc4RzKYNadcIIzxmzAIw.jejrYOHhqdr0oV7sNBL.IhGBo.mMOYEdevlnCp2OGku8.:15094:0:99999:7:::

bin:*:14790:0:99999:7:::

daemon:*:14790:0:99999:7:::

adm:*:14790:0:99999:7:::

八)CAP_FOWNER 3(以最后操作的UID,覆盖文件的先前的UID)
cp /etc/passwd /tmp/

ls -l /tmp/passwd   

-rw-r--r-- 1 root root 1171 2011-04-29 19:21 /tmp/passwd

授权cap_fowner权限给/usr/bin/vim

setcap cap_fowner=eip /usr/bin/vim

切换到test用户

su - test

编辑/tmp/passwd文件,存盘退出.

vi /tmp/passwd

修改文件,并保存退出.

查看/tmp/passwd,发现owner已经变成test

-rw-r--r-- 1 test test 1176 2011-04-29 19:21 /tmp/passwd

九)CAP_FSETID 4(确保在文件被修改后不修改setuid/setgid位)
起因是当文件被修改后,会清除掉文件的setuid/setgid位,而设定CAP_FSETID后将保证setuid/setgid位不被清除.但这对chown函数无用.

测试程序如下: 

1 #include <sys/types.h>
 2 #include <sys/types.h>
 3 #include <sys/stat.h>
 4 #include <fcntl.h>
 5 #include <sys/stat.h>
 6 #include <stdio.h>
 7 #include <stdlib.h>
 8 #include <string.h>
 9 main()
10 {
11         int handle;
12         char string[40];
13         int length, res;
14 
15         if ((handle = open("/tmp/passwd", O_WRONLY | O_CREAT | O_TRUNC,S_IREAD | S_IWRITE)) == -1)
16         {
17                 printf("Error opening file.\n");
18                 exit(1);
19         }
20         strcpy(string, "Hello, world!\n");
21         length = strlen(string);
22         if ((res = write(handle, string, length)) != length)
23         {
24                 printf("Error writing to the file.\n");
25                 exit(1);
26         }
27         printf("Wrote %d bytes to the file.\n", res);
28         close(handle);
29 }

gcc fsetid.c -o fsetid

先测试没有设FSETID的情况,如下:

chmod 6777 /tmp/passwd

ls -l /tmp/passwd

-rwsrwsrwx 1 test test 14 2011-04-30 14:22 /tmp/passwd

/tmp/fsetid 

Wrote 14 bytes to the file.

ls -l /tmp/passwd

-rwxrwxrwx 1 test test 14 2011-04-30 14:25 /tmp/passwd

我们看到setuid/setgid位被清除了.

下面是设定FSETID,如下:

chmod 6777 /tmp/passwd

ls -l /tmp/passwd     

-rwsrwsrwx 1 test test 14 2011-04-30 14:25 /tmp/passwd

切换到root用户,给/tmp/fsetid程序授权CAP_FSETID能力,如下:

setcap cap_fsetid=eip /tmp/fsetid

切换到普通用户

/tmp/fsetid

Wrote 14 bytes to the file.

ls -l /tmp/passwd

-rwsrwsrwx 1 test test 14 2011-04-30 14:28 /tmp/passwd

十)CAP_KILL 5 (允许对不属于自己的进程发送信号)
我们先模拟没有加CAP_KILL能力的情况,如下:

终端1,用root用户启用top程序,如下:

su - root

top

终端2,用test用户kill之前的top进程,如下:

pgrep top     

3114

/bin/kill 3114

kill: Operation not permitted

我们发现无法对不属于自己的进程发送信号.

下面我们用CAP_KILL能力的程序向不属于自己的进程发送信号,如下:

设定kill命令的kill位,如下:

setcap cap_kill=eip /bin/kill 

杀掉3114进程,没有问题,如下:

/bin/kill 3114

echo $?

0

注意:

普通用户要用/bin/kill这种绝对路径的方式,而不能用kill这种方式.

十一)CAP_SETGID 6 (设定程序允许普通用户使用setgid函数,这与文件的setgid权限位无关)
cp /etc/shadow /tmp/

chown root.root /tmp/shadow

chmod 640 /tmp/shadow

切换到普通用户test,并编写setgid测试程序,如下:

su - test

1 #include <unistd.h>
2 int
3 main ()
4 {
5         gid_t gid = 0;
6         setgid(gid);
7         system("/bin/cat /tmp/shadow");
8         return 0;
9 }

gcc setgid.c -o setgid

更改setgid程序为CAP_SETGID

setcap cap_setgid=eip /tmp/setgid

切换到普通用户,运行/tmp/setgid程序,如下:

su - test

/tmp/setgid 

root:11S9AmPHY8$ZIdORp6aLnYleb5EORxw8/:14479:0:99999:7:::

daemon:*:14479:0:99999:7:::

bin:*:14479:0:99999:7:::

sys:*:14479:0:99999:7:::

sync:*:14479:0:99999:7:::

games:*:14479:0:99999:7:::

man:*:14479:0:99999:7:::

lp:*:14479:0:99999:7:::

mail:*:14479:0:99999:7:::

news:*:14479:0:99999:7:::

uucp:*:14479:0:99999:7:::

proxy:*:14479:0:99999:7:::

www-data:*:14479:0:99999:7:::

backup:*:14479:0:99999:7:::

list:*:14479:0:99999:7:::

我们看到普通用户可以查看/tmp/shadow文件,而取消CAP_SETGID则使程序不能拥有setgid的权限,如下:

setcap -r /tmp/setgid

su - test

/tmp/setgid 

/bin/cat: /tmp/shadow: Permission denied

十二)CAP_SETUID 7 (设定程序允许普通用户使用setuid函数,这也文件的setuid权限位无关)
cp /etc/shadow /tmp/

chown root.root /tmp/shadow

chmod 640 /tmp/shadow

切换到普通用户test,并编写setuid测试程序,如下:

su - test

cd /tmp/

vi setuid.c

1 #include <unistd.h>
2 int
3 main ()
4 {
5         uid_t uid = 0;
6         setuid(uid);
7         system("/bin/cat /tmp/shadow");
8         return 0;
9 }

gcc setuid.c -o setuid

切换到root用户,更改setuid程序为CAP_SETUID

su - root

setcap cap_setuid=eip /tmp/setuid

切换到test用户,运行/tmp/setuid程序,如下:

su - test

/tmp/setuid

root:11S9AmPHY8$ZIdORp6aLnYleb5EORxw8/:14479:0:99999:7:::

daemon:*:14479:0:99999:7:::

bin:*:14479:0:99999:7:::

sys:*:14479:0:99999:7:::

sync:*:14479:0:99999:7:::

games:*:14479:0:99999:7:::

man:*:14479:0:99999:7:::

lp:*:14479:0:99999:7:::

我们看到普通用户可以查看/tmp/shadow文件,而取消CAP_SETUID则使程序不能拥有setuid的权限,如下:

setcap -r /tmp/setuid

su - test

/tmp/setuid 

/bin/cat: /tmp/shadow: Permission denied

十三)CAP_SETPCAP 8 (允许向其它进程转移能力以及删除其它进程的任意能力)
事实上只有init进程可以设定其它进程的能力,而其它程序无权对进程授权,root用户也不能对其它进程的能力进行修改,只能对当前进程通过cap_set_proc等函数进行修改,而子进程也会继承这种能力.

所以即使使用了CAP_SETPCAP能力,也不会起到真正的作用.

十四)CAP_LINUX_IMMUTABLE 9 (允许修改文件的不可修改(IMMUTABLE)和只添加(APPEND-ONLY)属性)
普通用户不能通过chattr对文件设置IMMUTABLE(chattr +i)和APPEND-ONLY(chattr +a)权限,而通过CAP_LINUX_IMMUTABLE可以使普通用户通过自己增减(immutable/append-only)权限.

普通用户通过chattr给文件增加immutable权限,如下:

touch /tmp/test

chattr +i /tmp/test

chattr: Operation not permitted while setting flags on /tmp/test

我们看到授权失败了,而如果我们对chattr增加了LINUX_IMMUTABLE权限,则可以成功,如下:

此时切换到root用户:

su - 

setcap cap_linux_immutable=eip /usr/bin/chattr 

切换到普通用户:

su - test

chattr +i /tmp/test

lsattr /tmp/test 

----i-------------- /tmp/test

我们看到授权成功了,注意,这里只能对自己的文件授权(immutable/append-only)权限,对于其它用户的权限LINUX_IMMUTABLE不起作用(root除外),如下:

su - test

chattr +i /etc/passwd

chattr: Permission denied while setting flags on /etc/passwd

十五)CAP_NET_BIND_SERVICE 10(允许绑定到小于1024的端口)
普通用户不能通过bind函数绑定小于1024的端口,而root用户可以做到,CAP_NET_BIND_SERVICE的作用就是让普通用户也可以绑端口到1024以下.

普通用户通过nc绑定端口500,如下:

nc -l -p 500

Can't grab 0.0.0.0:500 with bind : Permission denied

增加CAP_NET_BIND_SERVICE能力到nc程序,如下:

setcap cap_net_bind_service=eip /usr/bin/nc

再切换到普通用户,通过nc绑定端口500,如下:

nc -l -p 500

查看该端口:

netstat -tulnp|grep nc

tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      2523/nc       

十六)CAP_NET_BROADCAST 11(允许网络广播和多播访问)
事实上它并没有被应用,普通用户也可以使用ping -b 192.168.0.255也发送广播包

十七)CAP_NET_ADMIN 12(允许执行网络管理任务:接口,防火墙和路由等)
普通用户不能创建新的网络接口(interface),不能更改ip地址,而CAP_NET_ADMIN可以帮助普通用户完成这项工作,如下:

用普通用户创建新的网卡接口eth0:1失败

/sbin/ifconfig eth0:1 172.16.27.133 netmask 255.255.255.0

SIOCSIFADDR: Permission denied

SIOCSIFFLAGS: Permission denied

SIOCSIFNETMASK: Permission denied

此时我们把CAP_NET_ADMIN能力授权给ifconfig程序,如下:

setcap cap_net_admin=eip /sbin/ifconfig

我们再次用普通用户即可以新建网络接口eth0:1,并可以DOWN掉接口,如下:

/sbin/ifconfig eth0:1 172.16.27.133 netmask 255.255.255.0

/sbin/ifconfig eth0:1

eth0:1    Link encap:Ethernet  HWaddr 00:0c:29:f9:5e:06  

          inet addr:172.16.27.133  Bcast:172.16.27.255  Mask:255.255.255.0

          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

          Interrupt:18 Base address:0x1080 

/sbin/ifconfig eth0:1 down 

同样CAP_NET_ADMIN可以让普通用户增加/删除路由,如下:

/sbin/route add -host 192.168.27.139 gw 192.168.27.2

SIOCADDRT: Operation not permitted

授权NET_ADMIN,如下:

setcap cap_net_admin=eip /sbin/route

再次用普通用户增加路由,如下:

/sbin/route add -host 192.168.27.139 gw 192.168.27.2

/sbin/route  -n

Kernel IP routing table

Destination     Gateway         Genmask         Flags Metric Ref    Use Iface

192.168.27.139  192.168.27.2    255.255.255.255 UGH   0      0        0 eth0

192.168.27.0    0.0.0.0         255.255.255.0   U     0      0        0 eth0

0.0.0.0         192.168.27.2    0.0.0.0         UG    0      0        0 eth0

/sbin/route del -host 192.168.27.139 gw 192.168.27.2   

我们看到我们除了可以增加路由之外,也可以删除路由.

最后NET_ADMIN可以帮助我们让普通用户来管理防火墙.

普通用户不能用iptables来管理防火墙,如下:

/sbin/iptables -L -n

iptables v1.4.2: can't initialize iptables table `filter': Permission denied (you must be root)

Perhaps iptables or your kernel needs to be upgraded.

我们将CAP_NET_ADMIN授权给iptables程序,注意我们也要将CAP_NET_RAW授权给iptables,CAP_NET_RAW我们后面再解释,如下:

setcap cap_net_admin,cap_net_raw=eip /sbin/iptables-multi

此时就可以用普通用户来管理防火墙了,如下:

/sbin/iptables -A INPUT -p tcp -j ACCEPT

/sbin/iptables -L -n

Chain INPUT (policy ACCEPT)

target     prot opt source               destination         

ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           

Chain FORWARD (policy ACCEPT)

target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)

target     prot opt source               destination     

我们也可以删除防火墙策略,并清空当前的数据流量,如下:

/sbin/iptables -F

/sbin/iptables -Z

/sbin/iptables -X

 

十八)CAP_NET_RAW 13 (允许使用原始(raw)套接字)

原始套接字编程可以接收到本机网卡上的数据帧或者数据包,对监控网络流量和分析是很有作用的.

最常见的就是ping的实现,如下:

socket(PF_INET, SOCK_RAW, IPPROTO_ICMP) = 3

我们先把ping的setuid权限去掉

chmod u-s /bin/ping

ls -l /bin/ping

-rwxr-xr-x 1 root root 30788 2007-12-09 23:03 /bin/ping

用普通用户使用ping

ping  192.168.27.2  

ping: icmp open socket: Operation not permitted

提示没有权限,我们将ping授权CAP_NET_RAW能力,如下:

setcap cap_net_raw=eip /bin/ping

切换到普通用户再次ping 192.168.27.2,发现可以ping通,如下:

ping  192.168.27.2

PING 192.168.27.2 (192.168.27.2) 56(84) bytes of data.

64 bytes from 192.168.27.2: icmp_seq=1 ttl=128 time=0.266 ms

64 bytes from 192.168.27.2: icmp_seq=2 ttl=128 time=0.280 ms

64 bytes from 192.168.27.2: icmp_seq=3 ttl=128 time=0.319 ms

NET_RAW也同样用于tcpdump/iftop,一个普通用户无法使用tcpdump/iftop,而CAP_NET_RAW可以解决这个问题,方式同ping一样,所以我们不做演示.

十九)CAP_IPC_LOCK 14 (在允许锁定内存片段)
root和普通用户都可以用mlock来锁定内存,区别是root不受ulimit下的锁定内存大小限制,而普通用户会受到影响.

测试程序如下: 

1 #include <stdio.h>
 2 #include <sys/mman.h>
 3 
 4 int main(int argc, char* argv[])
 5 {
 6         int array[2048];
 7 
 8         if (mlock((const void *)array, sizeof(array)) == -1) {
 9                 perror("mlock: ");
10                 return -1;
11         }
12 
13         printf("success to lock stack mem at: %p, len=%zd\n",
14                         array, sizeof(array));
15 
16 
17         if (munlock((const void *)array, sizeof(array)) == -1) {
18                 perror("munlock: ");
19                 return -1;
20         }
21 
22         printf("success to unlock stack mem at: %p, len=%zd\n",
23                         array, sizeof(array));
24 
25         return 0;
26 }

gcc mlock.c -o mlock

切换到普通用户,我们看到mlock是不受限制

ulimit -a

max locked memory       (kbytes, -l) unlimited

我们运行程序

./mlock

success to lock stack mem at: 0xbfd94914, len=8192

success to unlock stack mem at: 0xbfd94914, len=8192

我们将限制改为1KB,再次运行程序,如下:

ulimit -l 1

./mlock

mlock: : Cannot allocate memory

切换到root用户,将CAP_IPC_LOCK能力授权给mlock测试程序,如下:

setcap cap_ipc_lock=eip /tmp/mlock

用普通用户再次运行程序,执行成功:

./mlock

success to lock stack mem at: 0xbfec1584, len=8192

success to unlock stack mem at: 0xbfec1584, len=8192

二十)CAP_IPC_OWNER 15 (忽略IPC所有权检查)
这个能力对普通用户有作用,如果用root用户创建共享内存(shmget),权限为600,而普通用户不能读取该段共享内存.

通过CAP_IPC_OWNER可以让普通用户的程序可以读取/更改共享内存.

我们用下面的程序创建共享内存段,并写入0xdeadbeef到共享内存段中.

 

1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <sys/ipc.h>
 6 #include <sys/shm.h>
 7 #include <sys/wait.h>
 8 
 9 void error_out(const char *msg)
10 {
11         perror(msg);
12         exit(EXIT_FAILURE);
13 }
14 
15 
16 int main (int argc, char *argv[])
17 {
18         key_t mykey = 12345678;
19 
20         const size_t region_size = sysconf(_SC_PAGE_SIZE);
21         int smid = shmget(mykey, region_size, IPC_CREAT|0600);
22         if(smid == -1)
23                 error_out("shmget");
24 
25         void *ptr;
26         ptr = shmat(smid, NULL, 0);
27         if (ptr == (void *) -1)
28                 error_out("shmat");
29         u_long *d = (u_long *)ptr;
30         *d = 0xdeadbeef;
31         printf("ipc mem %#lx\n", *(u_long *)ptr);
32 
33         return 0;
34 }

gcc test.c -o test -lrt

我们用root用户来执行本程序,创建共享内存,如下:

/tmp/test

ipc mem 0xdeadbeef

查看当前的共享内存

ipcs -m

------ Shared Memory Segments --------

key        shmid      owner      perms      bytes      nattch     status      

0x00bc614e 458752     root      600        4096       0              

修改程序,将d = 0xdeadbeef;改为d = 0xffffffff;

再编译,用普通用户运行程序,如下:

gcc test.c -o test -lrt

su - test

/tmp/test

shmget: Permission denied

我们看到没有权限更改共享内存.

用root用户把CAP_IPC_OWNER能力授权给test程序,如下:

setcap cap_ipc_owner=eip /tmp/test

再次用普通用户运行程序test,更改共享内存,如下:

/tmp/test

ipc mem 0xffffffff

我们看到修改成功,但要说明CAP_IPC_OWNER不能让进程/程序删除/脱离共享内存

二十一)CAP_SYS_MODULE 16 (允许普通用户插入和删除内核模块)
由于普通用户不能插入/删除内核模块,而CAP_SYS_MODULE可以帮助普通用户做到这点

例如,用户test插入内核模块nvram

/sbin/modprobe nvram      

FATAL: Error inserting nvram (/lib/modules/2.6.38.4/kernel/drivers/char/nvram.ko): Operation not permitted

系统提示权限不足.

我们把CAP_SYS_MODULE能力授权给modprobe程序,如下:

setcap cap_sys_module=eip /sbin/modprobe

再用普通用户加载内核模块,如下:

/sbin/modprobe nvram

lsmod |grep nvram

nvram                   3861  0 

我们看到可以加载.

同样我们可以将CAP_SYS_MODULE授权给rmmod程序,让其可以删除模块,如下:

setcap cap_sys_module=eip /sbin/rmmod

su - test

/sbin/rmmod nvram

lsmod |grep nvram

二十二)CAP_SYS_RAWIO 17 (允许用户打开端口,并读取修改端口数据,一般用ioperm/iopl函数)
ioperm只有低端的[0-0x3ff] I/O端口可被设置,且普通用户不能使用.

iopl可以用于所有的65536个端口,因此ioperm相当于iopl调用的一个子集.

下面的程序首先设置0x3FF端口的读写权限,然后读出原先的值,然后将原值的LSB翻转并写回端口,并在此读取端口值.

 

1 #include <unistd.h>
 2 #include <sys/io.h>
 3 
 4 #define PORT_ADDR 0x3FF
 5 
 6 int main(void)
 7 {
 8       int ret;
 9       char port_val;
10       
11       /*set r/w permission of all 65536 ports*/
12       ret = iopl(3);
13       if(ret < 0){
14            perror("iopl set error");
15            return 0;
16       }
17 
18       port_val = inb(PORT_ADDR);
19       printf("Original value of port 0x%x is : %.2x\n", PORT_ADDR, port_val);
20       
21       /*reverse the least significant bit */
22 
23       outb(port_val^0x01, PORT_ADDR);
24       port_val = inb(PORT_ADDR);
25       printf("Current value of port 0x%x is : %.2x\n", PORT_ADDR, port_val);
26       
27       /*set r/w permission of  all 65536 ports*/
28       
29       ret = iopl(0);
30       if(ret < 0){
31            perror("iopl set error");
32            return 0;
33       }
34       return 0;
35 }

编译:

gcc iopl.c -o iopl

普通用户运行iopl程序,提示没有权限.

/tmp/iopl 

iopl set error: Operation not permitted

给程序iopl授权CAP_SYS_RAWIO能力,此时普通用户可以执行iopl程序,如下:

setcap cap_sys_rawio=eip /tmp/iopl

su - test

/tmp/iopl 

Original value of port 0x3ff is : 01

Current value of port 0x3ff is : 00

/tmp/iopl 

Original value of port 0x3ff is : 00

Current value of port 0x3ff is : 01

二十三)CAP_SYS_CHROOT 18 (允许使用chroot()系统调用)
普通用户不能通过chroot系统调用更改程式执行时所参考的根目录位置,而CAP_SYS_CHROOT可以帮助普通用户做到这一点.

普通用户使用chroot,如下:

/usr/sbin/chroot / /bin/bash

/usr/sbin/chroot: cannot change root directory to /: Operation not permitted

通过root授权CAP_SYS_CHROOT能力给chroot程序,如下:

capset cap_sys_chroot=eip /usr/sbin/chroot

普通用户再次用chroot切换根目录,如下:

/usr/sbin/chroot / /bin/sh

sh-3.2$

二十四)CAP_SYS_PTRACE 19 (允许跟踪任何进程)
普通用户不能跟踪任何进程,不包括它自己的进程,而CAP_SYS_PTRACE可以帮助普通用户跟踪任何进程.

切换到普通用户,跟踪PID1的进程.

strace -p 1

attach: ptrace(PTRACE_ATTACH, ...): Operation not permitted

切换到root用户,将CAP_SYS_PTRACE能力授权给strace程序,用于跟踪进程,如下:

setcap cap_sys_ptrace=eip /usr/bin/strace

切换到普通用户,跟踪PID1的进程,如下:

strace -p 1

Process 1 attached - interrupt to quit

select(11, [10], NULL, NULL, {3, 771381}) = 0 (Timeout)

time(NULL)                              = 1304348451

stat64("/dev/initctl", {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0

fstat64(10, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0

stat64("/dev/initctl", {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0

select(11, [10], NULL, NULL, {5, 0})    = 0 (Timeout)

time(NULL)                              = 1304348456

二十五)CAP_SYS_PACCT 20 (允许配置进程记帐process accounting) 
要完成进程记帐,要保证有写入文件的权限,这里我们将进程记录写入到/home/test/log/psacct.

mkdir /home/test/log/

touch /home/test/log/psacct

程序通过acct函数,将进程的记帐写入到指定文件中,如果acct函数的参数为NULL,则关闭进程记帐.

 1 #include <stdlib.h>
 2 #include <sys/acct.h>
 3 
 4 int
 5 main()
 6 {
 7         int ret;
 8         ret = acct("/home/test/log/pacct");
 9         if(ret < 0){
10            perror("acct on error");
11            return 0;
12         }
13         system("/bin/ls -l");
14         acct(NULL);
15         if(ret < 0){
16            perror("acct off error");
17            return 0;
18         }
19         return 0;
20 } 

gcc psacct.c -o psacct

./psacct 

acct on error: Operation not permitted

给psacct程序授权CAP_SYS_PACCT能力,如下:

setcap cap_sys_psacct /home/test/psacct

注意这里的能力是sys_psacct,而不是sys_pacct

回到普通用户,执行psacct

./psacct 

total 24

drwxr-xr-x 2 test test 4096 2011-05-02 13:28 log

-rw-r--r-- 1 test test   64 2011-05-02 13:25 pacct

-rwxr-xr-x 1 test test 6590 2011-05-02 13:31 psacct

-rw-r--r-- 1 test test  314 2011-05-02 13:31 psacct.c

我们看到已经执行成功,下面我们再看下进程记录,如下:

lastcomm -f /home/test/log/pacct 

ls                     test     pts/0      0.03 secs Mon May  2 13:33

二十六)CAP_SYS_ADMIN 21 (允许执行系统管理任务,如挂载/卸载文件系统,设置磁盘配额,开/关交换设备和文件等)
下面是普通用户拥有相关管理权限的测试

1)更改主机名

setcap cap_sys_admin=eip /bin/hostname

su - test

hostname test2

hostname

test2

2)挂载/卸载文件系统

这里我们注意,系统的mount命令不能做这个试验,因为程序中做了判断,如果不是root用户请退出.所以我们用mount()函数来完成这个试验,程序如下:

 1 #include <stdlib.h>
 2 #include <sys/mount.h>
 3 int
 4 main ()
 5 {
 6         int ret;
 7         ret = mount("/dev/sda1", "/mnt/", "ext3", MS_MGC_VAL, NULL);
 8         if(ret < 0){
 9            perror("mount error");
10            return 0;
11         }
12         return 0;
13 }

用普通用户编译运行:

gcc mounttest.c -o mounttest

./mounttest 

mount error: Operation not permitted

我们看到普通用户不能完成mount操作,而CAP_SYS_ADMIN可以帮助普通用户完成该操作,如下:

setcap cap_sys_admin=eip /home/test/mounttest 

切换到普通用户,执行mounttest程序,如下:

./mounttest 

cat /proc/mounts 

/dev/sda1 /mnt ext3 rw,relatime,errors=remount-ro,barrier=0,data=writeback 0 0

umount和mount一样,我们在这里不做演示.

3)swapon/swapoff

普通用户不能进行swapon/swapoff操作,而CAP_SYS_ADMIN可以帮助普通用户完成swapon/swapoff操作,如下:

dd if=/dev/zero of=/tmp/testdb bs=10M count=1

1+0 records in

1+0 records out

10485760 bytes (10 MB) copied, 0.164669 s, 63.7 MB/s

/sbin/mkswap /tmp/testdb

Setting up swapspace version 1, size = 10481 kB

no label, UUID=0ff46dc8-781c-4c3f-81b3-fe860f74793e

/sbin/swapon /tmp/testdb

swapon: /tmp/testdb: Operation not permitted

我们看到swapon操作被拒绝,这里我们对swapon进行授权,如下:

setcap cap_sys_admin /sbin/swapon

普通用户再次swapon,如下:

/sbin/swapon /tmp/testdb

/sbin/swapon 

/sbin/swapon -s

Filename                                Type            Size    Used    Priority

/dev/sda6                               partition       7815584 0       -1

/tmp/testdb                             file            10236   0       -2

我们看到swapon操作成功.因为swapoff是swapon的软链接,所以可以直接swapoff掉交换分区,如下:

/sbin/swapoff /tmp/testdb

/sbin/swapon -s

Filename                                Type            Size    Used    Priority

/dev/sda6                               partition       7815584 0       -1

ls -l /sbin/swapoff    

lrwxrwxrwx 1 root root 6 2009-08-23 07:49 /sbin/swapoff -> swapon

二十七) CAP_SYS_BOOT 22 (允许普通用使用reboot()函数)
这里我们无法用reboot命令来做测试,因为reboot命令做了判断,只允许root(UID=0)的用户可以使用.

我们用下面的程序进行测试.

1 #include <unistd.h>
2 #include <sys/reboot.h>
3 int main()
4 {
5     sync(); 
6     return reboot(RB_AUTOBOOT);
7 }

编译:

gcc reboot1.c -o reboot1

./reboot1

这时系统没有重启.

我们查看程序的返回码,这里是255,说明程序没有运行成功.

echo $?

255

我们用CAP_SYS_BOOT能力使reboot1程序可以被普通用户重启,如下:

setcap cap_sys_boot=eip /home/test/reboot1

用普通用户再试运行reboot1,如下:

reboot1

此时系统被重启了.

二十八)CAP_SYS_NICE 23(允许提升优先级,设置其它进程的优先级)
对于普通用户程序的NICE优先级,不能超过ulimit对它的限制,如下:

nice -n -5 ls 

nice: cannot set niceness: Permission denied

而CAP_SYS_NICE可以帮助普通用户设置一个想要的一个任意优先级.

setcap cap_sys_nice=eip /usr/bin/nice

切换到普通用户,指定优先级,如下:

nice -n -5 ls 

log  mnt  mount.c  mounttest  pacct  psacct  psacct.c  reboot1  reboot1.c  test

普通用户也不能给指定进程指定NICE优先级,而CAP_SYS_NICE也可以做的,如下:

renice -5 2255

renice: 2255: setpriority: Operation not permitted

setcap cap_sys_nice=eip /usr/bin/renice

renice -5 2255

2255: old priority 0, new priority -5

我们甚至可以用CAP_SYS_NICE来指定实时优先级

chrt -f 50  ls

chrt: failed to set pid 0's policy: Operation not permitted

给/usr/bin/chrt命令授权CAP_SYS_NICE能力,如下:

setcap  cap_sys_nice=eip  /usr/bin/chrt

切换到普通用户,如下:

chrt -f 50  ls

log  mnt  setulimit  setulimit.c

我们也可以指定它的CPU亲和性,如下:

taskset -p 1 2255

pid 2255's current affinity mask: 1

sched_setaffinity: Operation not permitted

failed to set pid 2255's affinity.

给/usr/bin/taskset命令授权CAP_SYS_NICE能力,如下:

setcap  cap_sys_nice=eip  /usr/bin/taskset

切换到普通用户,再次运行taskset,可以成功的设置CPU亲和性

taskset -p 1 2255

pid 2255's current affinity mask: 1

pid 2255's new affinity mask: 1

二十九) CAP_SYS_RESOURCE 24 忽略资源限制
普通用户不能用setrlimit()来突破ulimit的限制
我们用下面的程序进行测试,普通用户是无法修改RLIMIT_STACK(堆栈大小)的.如下:

 

1 #include <signal.h>
 2 #include <stdio.h>
 3 #include <string.h>
 4 #include <stdlib.h>
 5 #include <limits.h>
 6 #include <unistd.h>
 7 #include <sys/types.h>
 8 #include <sys/stat.h>
 9 #include <sys/resource.h>
10 
11 int
12 main (int argc, char *argv[])
13 {
14 int r = 0;
15 struct rlimit rl;
16 
17 getrlimit (RLIMIT_STACK,&rl);
18 
19 printf("crrent hard limit is %ld\n",
20 (u_long) rl.rlim_max);
21 
22 rl.rlim_max = rl.rlim_max+1;
23 r = setrlimit (RLIMIT_STACK, &rl);
24 if (r){
25 perror("setrlimit");
26 return -1;
27 }
28 
29 printf("limit set to %ld \n", (u_long) rl.rlim_max+1);
30 return 0;
31 } 

gcc setulimit.c -o setulimit

我们先来查看当前的限制,这里是10MB,如下:

ulimit -H -s 

10240

./setulimit    

crrent hard limit is 10485760

setrlimit: Operation not permitted

我们给setulimit程序以CAP_SYS_RESOURCE的能力,如下:

setcap cap_sys_resource=eip /home/test/setulimit 

用普通用户再次运行程序,已经可以通过setrlimit()设定超过限额了,如下:

./setulimit 

crrent hard limit is 10485760

limit set to 10485762 

同样我们也可以用CAP_SYS_RESOURCE能力使程序超出磁盘限额,如下:

quotacheck -avug

quotaon -avug

edquota -u test

Filesystem                   blocks       soft       hard     inodes     soft     hard

  /dev/sda7                         0          3          5          0        0        0

mkdir /export/test

chown -R test.test /export/test/

切换到普通用户,用dd命令产生3MB的文件,这明显超过了5kb的限制,如下:

su - test

dd if=/dev/zero of=/export/test/test bs=1M count=3

sda7: warning, user block quota exceeded.

sda7: write failed, user block limit reached.

dd: writing `test': Disk quota exceeded

1+0 records in

0+0 records out

4096 bytes (4.1 kB) copied, 0.0117371 s, 349 kB/s

授权CAP_SYS_RESOURCE能力给/bin/dd命令,如下:

setcap cap_sys_resource=eip /bin/dd

再次用普通用户运行dd命令,即可以超过5kb的限额了.

su - test

dd if=/dev/zero of=test bs=1M count=3

sda7: warning, user block quota exceeded.

3+0 records in

3+0 records out

3145728 bytes (3.1 MB) copied, 0.423662 s, 7.4 MB/s

三十)CAP_SYS_TIME 25(允许改变系统时钟)
普通用户不能改变系统时钟,如下:

date -s 2012-01-01

date: cannot set date: Operation not permitted

Sun Jan  1 00:00:00 EST 2012

CAP_SYS_TIME可以帮助普通用户改变系统时钟,如下:

setcap cap_sys_time=eip /bin/date

切换到普通用户再次改变时间,发现已经可以改变了

su - test

date -s 2012-01-01

Sun Jan  1 00:00:00 EST 2012

date

Sun Jan  1 00:00:02 EST 2012

三十一)CAP_SYS_TTY_CONFIG 26(允许配置TTY设备)
我们下面用vhangup()函数来挂起当前的tty

程序如下:

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 
 4 int main ()
 5 {
 6   int r;
 7   r=vhangup();
 8   if (r){ 
 9   perror ("vhanguo");
10   }
11   return 0;
12 }

gcc vhup.c -o vhup

./vhup 

vhanguo: Operation not permitted

我们给vhup程序设定CAP_SYS_TTY_CONFIG能力,如下:

setcap cap_sys_tty_config=eip /home/test/vhup

再次用普通用户执行程序vhup,tty被挂起,如下:

./vhup 

此时当前tty被挂起.

三十二) CAP_MKNOD 27 (允许使用mknod系统调用)
普通用户不能用mknod()来创建设备文件,而CAP_MKNOD可以帮助普通用户做到这一点,如下:

mknod /tmp/tnod1 c 1 5

mknod: `/tmp/tnod1': Operation not permitted

setcap cap_mknod=eip /bin/mknod

切换到普通用户,再次用mknod命令创建设备文件,如下:

mknod /tmp/tnod1 c 1 5

ls -l /tmp/tnod1 

crw-r--r-- 1 test test 1, 5 2012-01-01 00:31 /tmp/tnod1

三十三) CAP_LEASE 28(允许在文件上建立租借锁)
系统调用fcntl()可以用于租借锁,此时采用的函数原型如下:

       int fcntl(int fd, int cmd, long arg);

与租借锁相关的 cmd 参数的取值有两种:F_SETLEASE 和 F_GETLEASE。其含义如下所示:

F_SETLEASE:根据下面所描述的 arg 参数指定的值来建立或者删除租约:

F_RDLCK:设置读租约。当文件被另一个进程以写的方式打开时,拥有该租约的当前进程会收到通知

F_WRLCK:设置写租约。当文件被另一个进程以读或者写的方式打开时,拥有该租约的当前进程会收到通知

F_UNLCK:删除以前建立的租约

F_GETLEASE:表明调用进程拥有文件上哪种类型的锁,这需要通过返回值来确定,返回值有三种:F_RDLCK、F_WRLCK和F_UNLCK,分别表明调用进程对文件拥有读租借、写租借或者根本没有租借

某个进程可能会对文件执行其他一些系统调用(比如 OPEN() 或者 TRUNCATE()),如果这些系统调用与该文件上由 F_SETLEASE 所设置的租借锁相冲突,内核就会阻塞这个系统调用;

同时,内核会给拥有这个租借锁的进程发信号,告知此事。拥有此租借锁的进程会对该信号进行反馈,它可能会删除这个租借锁,也可能会减短这个租借锁的租约,从而可以使得该文件可以被其他进程所访问。

如果拥有租借锁的进程不能在给定时间内完成上述操作,那么系统会强制帮它完成。通过 F_SETLEASE 命令将 arg 参数指定为 F_UNLCK 就可以删除这个租借锁。

不管对该租借锁减短租约或者干脆删除的操作是进程自愿的还是内核强迫的,只要被阻塞的系统调用还没有被发出该调用的进程解除阻塞,那么系统就会允许这个系统调用执行。

即使被阻塞的系统调用因为某些原因被解除阻塞,但是上面对租借锁减短租约或者删除这个过程还是会执行的。

源程序如下:

 

1 #define _GNU_SOURCE 
 2 
 3 #include <unistd.h>
 4 #include <fcntl.h>
 5 #include <stdio.h>
 6 #include <sys/file.h>
 7 static void show_lease(int fd)
 8 {
 9         int res;
10 
11         res = fcntl(fd, F_GETLEASE);
12         switch (res) {
13                 case F_RDLCK:
14                         printf("Read lease\n");
15                         break;
16                 case F_WRLCK:
17                         printf("Write lease\n");
18                         break;
19                 case F_UNLCK:
20                         printf("No leases\n");
21                         break;
22                 default:
23                         printf("Some shit\n");
24                         break;
25         }
26 }
27 
28 int main(int argc, char **argv)
29 {
30         int fd, res;
31 
32         fd = open(argv[1], O_RDONLY);
33         if (fd == -1) {
34                 perror("Can't open file");
35                 return 1;
36         }
37 
38         res = fcntl(fd, F_SETLEASE, F_WRLCK);
39         if (res == -1) {
40                 perror("Can't set lease");
41                 return 1;
42         }
43 
44         show_lease(fd);
45 
46         if (flock(fd, LOCK_SH) == -1) {
47                 perror("Can't flock shared");
48                 return 1;
49         }
50 
51         show_lease(fd);
52 
53         return 0;
54 }

编译:

gcc fcntl.c -o fcntl

我们使用普通用户在/etc/passwd文件上建立租借锁,由于普通用户没有/etc/passwd上建租借锁的权限,故而报错,如下:

su - test

/tmp/fcntl /etc/passwd

Can't set lease: Permission denied

注:

普通用户可以在自己的文件上(owner)建立租借锁.

授权lease给/tmp/fcntl文件,如下:

setcap cap_lease=eip /tmp/fcntl

再次运行/tmp/fcntl,可以建立租借锁了,如下:

/tmp/fcntl /etc/passwd

Write lease

Write lease

三十四)CAP_SETFCAP 31 (允许在指定的程序上授权能力给其它程序)
例如我们让普通用户也能用setcap给其它程序授权,如下:

setcap CAP_SETFCAP=eip /usr/sbin/setcap 

su - test

setcap CAP_SETPCAP=eip /bin/ls

getcap /bin/ls                

/bin/ls = cap_setpcap+eip

三十六)其它的能力
CAP_AUDIT_WRITE

CAP_AUDIT_CONTROL

CAP_MAC_OVERRIDE

CAP_MAC_ADMIN

CAP_SYSLOG

这五组只能涉及到syslog,audit,mac等安全模块,以后专门对其进行分析.

三十七)总结:
CAP_CHOWN 0 允许改变文件的所有权 

CAP_DAC_OVERRIDE 1 忽略对文件的所有DAC访问限制 

CAP_DAC_READ_SEARCH 2 忽略所有对读、搜索操作的限制 

CAP_FOWNER 3 以最后操作的UID,覆盖文件的先前的UID

CAP_FSETID 4 确保在文件被修改后不修改setuid/setgid位

CAP_KILL 5 允许对不属于自己的进程发送信号 

CAP_SETGID 6 允许改变组ID 

CAP_SETUID 7 允许改变用户ID 

CAP_SETPCAP 8 允许向其它进程转移能力以及删除其它进程的任意能力(只限init进程)

CAP_LINUX_IMMUTABLE 9 允许修改文件的不可修改(IMMUTABLE)和只添加(APPEND-ONLY)属性 

CAP_NET_BIND_SERVICE 10 允许绑定到小于1024的端口 

CAP_NET_BROADCAST 11 允许网络广播和多播访问(未使用) 

CAP_NET_ADMIN 12 允许执行网络管理任务:接口、防火墙和路由等.

CAP_NET_RAW 13 允许使用原始(raw)套接字 

CAP_IPC_LOCK 14 允许锁定共享内存片段 

CAP_IPC_OWNER 15 忽略IPC所有权检查 

CAP_SYS_MODULE 16 插入和删除内核模块 

CAP_SYS_RAWIO 17 允许对ioperm/iopl的访问 

CAP_SYS_CHROOT 18 允许使用chroot()系统调用 

CAP_SYS_PTRACE 19 允许跟踪任何进程 

CAP_SYS_PACCT 20 允许配置进程记帐(process accounting) 

CAP_SYS_ADMIN 21 允许执行系统管理任务:加载/卸载文件系统、设置磁盘配额、开/关交换设备和文件等.

CAP_SYS_BOOT 22 允许重新启动系统 

CAP_SYS_NICE 23 允许提升优先级,设置其它进程的优先级 

CAP_SYS_RESOURCE 24 忽略资源限制 

CAP_SYS_TIME 25 允许改变系统时钟 

CAP_SYS_TTY_CONFIG 26 允许配置TTY设备 

CAP_MKNOD 27 允许使用mknod()系统调用 

CAP_LEASE 28 允许在文件上建立租借锁

CAP_SETFCAP 31 允许在指定的程序上授权能力给其它程序

参考:

man capabilities

linux-2.6.38.5/include/linux/capability.h 
Referenced from:https://www.cnblogs.com/sky-heaven/p/12096758.html