summaryrefslogtreecommitdiff
path: root/src/gen_pfc.c
blob: d5bcdbf2d1455d6167e4112beee54a014a3bd329 (plain)
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
/**
 * Seccomp Pseudo Filter Code (PFC) Generator
 *
 * Copyright (c) 2012 Red Hat <pmoore@redhat.com>
 * Author: Paul Moore <pmoore@redhat.com>
 */

/*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <asm/bitsperlong.h>

#include <seccomp.h>

#include "db.h"
#include "gen_pfc.h"

/* XXX - we should check the fprintf() return values */

/**
 * Display a string representation of the filter action
 * @param fds the file stream to send the output
 * @param action the action
 */
static void _pfc_action(FILE *fds, uint32_t action)
{
	switch (action & 0xffff0000) {
	case SCMP_ACT_KILL:
		fprintf(fds, " action KILL;\n");
		break;
	case SCMP_ACT_TRAP:
		fprintf(fds, " action TRAP;\n");
		break;
	case SCMP_ACT_ERRNO(0):
		fprintf(fds, " action ERRNO(%u);\n", (action & 0x0000ffff));
		break;
	case SCMP_ACT_TRACE(0):
		fprintf(fds, " action TRACE(%u);\n", (action & 0x0000ffff));
		break;
	case SCMP_ACT_ALLOW:
		fprintf(fds, " action ALLOW;\n");
		break;
	default:
		fprintf(fds, " action 0x%x;\n", action);
	}
}

/**
 * Indent the output stream
 * @param fds the file stream to send the output
 * @param lvl the indentation level
 *
 * This function indents the output stream with whitespace based on the
 * requested indentation level.
 */
static void _indent(FILE *fds, unsigned int lvl)
{
	while (lvl-- > 0)
		fprintf(fds, " ");
}

/**
 * Generate the pseudo filter code for an argument chain
 * @param node the head of the argument chain
 * @param lvl the indentation level
 * @param fds the file stream to send the output
 *
 * This function generates the pseudo filter code representation of the given
 * argument chain and writes it to the given output stream.
 *
 */
static void _gen_pfc_chain(const struct db_arg_chain_tree *node,
			   unsigned int lvl, FILE *fds)
{
	const struct db_arg_chain_tree *c_iter;

	/* get to the start */
	c_iter = node;
	while (c_iter->lvl_prv != NULL)
		c_iter = c_iter->lvl_prv;

	while (c_iter != NULL) {
		/* comparison operation */
		_indent(fds, lvl);
		switch (c_iter->op) {
			case SCMP_CMP_NE:
				fprintf(fds, " if ($a%d != %"PRIu64")\n",
					c_iter->arg,
					c_iter->datum);
				break;
			case SCMP_CMP_LT:
				fprintf(fds, " if ($a%d < %"PRIu64")\n",
					c_iter->arg,
					c_iter->datum);
				break;
			case SCMP_CMP_LE:
				fprintf(fds, " if ($a%d <= %"PRIu64")\n",
					c_iter->arg,
					c_iter->datum);
				break;
			case SCMP_CMP_EQ:
				fprintf(fds, " if ($a%d == %"PRIu64")\n",
					c_iter->arg,
					c_iter->datum);
				break;
			case SCMP_CMP_GE:
				fprintf(fds, " if ($a%d >= %"PRIu64")\n",
					c_iter->arg,
					c_iter->datum);
				break;
			case SCMP_CMP_GT:
				fprintf(fds, " if ($a%d > %"PRIu64")\n",
					c_iter->arg,
					c_iter->datum);
				break;
			default:
				fprintf(fds, " if ($a%d ??? %"PRIu64")\n",
					c_iter->arg, c_iter->datum);
		}

		/* true result */
		if (c_iter->act_t_flg) {
			_indent(fds, lvl + 1);
			_pfc_action(fds, c_iter->act_t);
		} else if (c_iter->nxt_t != NULL)
			_gen_pfc_chain(c_iter->nxt_t, lvl + 1, fds);

		/* false result */
		if (c_iter->act_f_flg) {
			_indent(fds, lvl);
			fprintf(fds, " else\n");
			_indent(fds, lvl + 1);
			_pfc_action(fds, c_iter->act_f);
		} else if (c_iter->nxt_f != NULL) {
			_indent(fds, lvl);
			fprintf(fds, " else\n");
			_gen_pfc_chain(c_iter->nxt_f, lvl + 1, fds);
		}

		c_iter = c_iter->lvl_nxt;
	}
}

/**
 * Generate pseudo filter code for a syscall
 * @param sys the syscall filter
 * @param fds the file stream to send the output
 *
 * This function generates a pseduo filter code representation of the given
 * syscall filter and writes it to the given output stream.
 *
 */
static void _gen_pfc_syscall(const struct db_sys_list *sys, FILE *fds)
{
	unsigned int sys_num = sys->num;

	fprintf(fds, "# filter code for syscall #%d\n", sys_num);
	if (sys->chains != NULL) {
		fprintf(fds, " if ($syscall != %d) goto syscal_%d_end;\n",
			sys_num, sys_num);
		_gen_pfc_chain(sys->chains, 0, fds);
		fprintf(fds, " syscall_%d_end:\n", sys_num);
	} else {
		fprintf(fds, " if ($syscall == %d)", sys_num);
		_pfc_action(fds, sys->action);
	}
}

/**
 * Generate a pseudo filter code string representation
 * @param db the seccomp filter DB
 * @param fd the fd to send the output
 *
 * This function generates a pseudo filter code representation of the given
 * filter DB and writes it to the given fd.  Returns zero on success, negative
 * values on failure.
 *
 */
int gen_pfc_generate(const struct db_filter *db, int fd)
{
	int newfd;
	FILE *fds;
	struct db_sys_list *s_iter;

	newfd = dup(fd);
	if (newfd < 0)
		return errno;
	fds = fdopen(newfd, "a");
	if (fds == NULL) {
		close(newfd);
		return errno;
	}

	fprintf(fds, "#\n");
	fprintf(fds, "# pseudo filter code start\n");
	fprintf(fds, "#\n");
	db_list_foreach(s_iter, db->syscalls) {
		if (s_iter->valid == 0)
			continue;
		_gen_pfc_syscall(s_iter, fds);
	}
	fprintf(fds, "# default action\n");
	_pfc_action(fds, db->def_action);
	fprintf(fds, "#\n");
	fprintf(fds, "# pseudo filter code end\n");
	fprintf(fds, "#\n");

	fflush(fds);
	fclose(fds);
	return 0;
}