xref: /openbmc/linux/samples/landlock/sandboxer.c (revision f6e53fb2)
1ba84b0bfSMickaël Salaün // SPDX-License-Identifier: BSD-3-Clause
2ba84b0bfSMickaël Salaün /*
3ba84b0bfSMickaël Salaün  * Simple Landlock sandbox manager able to launch a process restricted by a
4ba84b0bfSMickaël Salaün  * user-defined filesystem access control policy.
5ba84b0bfSMickaël Salaün  *
6ba84b0bfSMickaël Salaün  * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
7ba84b0bfSMickaël Salaün  * Copyright © 2020 ANSSI
8ba84b0bfSMickaël Salaün  */
9ba84b0bfSMickaël Salaün 
10ba84b0bfSMickaël Salaün #define _GNU_SOURCE
11ba84b0bfSMickaël Salaün #include <errno.h>
12ba84b0bfSMickaël Salaün #include <fcntl.h>
13ba84b0bfSMickaël Salaün #include <linux/landlock.h>
14ba84b0bfSMickaël Salaün #include <linux/prctl.h>
15ba84b0bfSMickaël Salaün #include <stddef.h>
16ba84b0bfSMickaël Salaün #include <stdio.h>
17ba84b0bfSMickaël Salaün #include <stdlib.h>
18ba84b0bfSMickaël Salaün #include <string.h>
19ba84b0bfSMickaël Salaün #include <sys/prctl.h>
20ba84b0bfSMickaël Salaün #include <sys/stat.h>
21ba84b0bfSMickaël Salaün #include <sys/syscall.h>
22ba84b0bfSMickaël Salaün #include <unistd.h>
23ba84b0bfSMickaël Salaün 
24ba84b0bfSMickaël Salaün #ifndef landlock_create_ruleset
2581709f3dSMickaël Salaün static inline int
landlock_create_ruleset(const struct landlock_ruleset_attr * const attr,const size_t size,const __u32 flags)2681709f3dSMickaël Salaün landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
27ba84b0bfSMickaël Salaün 			const size_t size, const __u32 flags)
28ba84b0bfSMickaël Salaün {
29ba84b0bfSMickaël Salaün 	return syscall(__NR_landlock_create_ruleset, attr, size, flags);
30ba84b0bfSMickaël Salaün }
31ba84b0bfSMickaël Salaün #endif
32ba84b0bfSMickaël Salaün 
33ba84b0bfSMickaël Salaün #ifndef landlock_add_rule
landlock_add_rule(const int ruleset_fd,const enum landlock_rule_type rule_type,const void * const rule_attr,const __u32 flags)34ba84b0bfSMickaël Salaün static inline int landlock_add_rule(const int ruleset_fd,
35ba84b0bfSMickaël Salaün 				    const enum landlock_rule_type rule_type,
3681709f3dSMickaël Salaün 				    const void *const rule_attr,
3781709f3dSMickaël Salaün 				    const __u32 flags)
38ba84b0bfSMickaël Salaün {
3981709f3dSMickaël Salaün 	return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
4081709f3dSMickaël Salaün 		       flags);
41ba84b0bfSMickaël Salaün }
42ba84b0bfSMickaël Salaün #endif
43ba84b0bfSMickaël Salaün 
44ba84b0bfSMickaël Salaün #ifndef landlock_restrict_self
landlock_restrict_self(const int ruleset_fd,const __u32 flags)45ba84b0bfSMickaël Salaün static inline int landlock_restrict_self(const int ruleset_fd,
46ba84b0bfSMickaël Salaün 					 const __u32 flags)
47ba84b0bfSMickaël Salaün {
48ba84b0bfSMickaël Salaün 	return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
49ba84b0bfSMickaël Salaün }
50ba84b0bfSMickaël Salaün #endif
51ba84b0bfSMickaël Salaün 
52ba84b0bfSMickaël Salaün #define ENV_FS_RO_NAME "LL_FS_RO"
53ba84b0bfSMickaël Salaün #define ENV_FS_RW_NAME "LL_FS_RW"
54ba84b0bfSMickaël Salaün #define ENV_PATH_TOKEN ":"
55ba84b0bfSMickaël Salaün 
parse_path(char * env_path,const char *** const path_list)56ba84b0bfSMickaël Salaün static int parse_path(char *env_path, const char ***const path_list)
57ba84b0bfSMickaël Salaün {
58ba84b0bfSMickaël Salaün 	int i, num_paths = 0;
59ba84b0bfSMickaël Salaün 
60ba84b0bfSMickaël Salaün 	if (env_path) {
61ba84b0bfSMickaël Salaün 		num_paths++;
62ba84b0bfSMickaël Salaün 		for (i = 0; env_path[i]; i++) {
63ba84b0bfSMickaël Salaün 			if (env_path[i] == ENV_PATH_TOKEN[0])
64ba84b0bfSMickaël Salaün 				num_paths++;
65ba84b0bfSMickaël Salaün 		}
66ba84b0bfSMickaël Salaün 	}
67ba84b0bfSMickaël Salaün 	*path_list = malloc(num_paths * sizeof(**path_list));
68ba84b0bfSMickaël Salaün 	for (i = 0; i < num_paths; i++)
69ba84b0bfSMickaël Salaün 		(*path_list)[i] = strsep(&env_path, ENV_PATH_TOKEN);
70ba84b0bfSMickaël Salaün 
71ba84b0bfSMickaël Salaün 	return num_paths;
72ba84b0bfSMickaël Salaün }
73ba84b0bfSMickaël Salaün 
749805a722SMickaël Salaün /* clang-format off */
759805a722SMickaël Salaün 
76ba84b0bfSMickaël Salaün #define ACCESS_FILE ( \
77ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_EXECUTE | \
78ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_WRITE_FILE | \
79faeb9197SGünther Noack 	LANDLOCK_ACCESS_FS_READ_FILE | \
80faeb9197SGünther Noack 	LANDLOCK_ACCESS_FS_TRUNCATE)
81ba84b0bfSMickaël Salaün 
829805a722SMickaël Salaün /* clang-format on */
839805a722SMickaël Salaün 
populate_ruleset(const char * const env_var,const int ruleset_fd,const __u64 allowed_access)8481709f3dSMickaël Salaün static int populate_ruleset(const char *const env_var, const int ruleset_fd,
85ba84b0bfSMickaël Salaün 			    const __u64 allowed_access)
86ba84b0bfSMickaël Salaün {
87ba84b0bfSMickaël Salaün 	int num_paths, i, ret = 1;
88ba84b0bfSMickaël Salaün 	char *env_path_name;
89ba84b0bfSMickaël Salaün 	const char **path_list = NULL;
90ba84b0bfSMickaël Salaün 	struct landlock_path_beneath_attr path_beneath = {
91ba84b0bfSMickaël Salaün 		.parent_fd = -1,
92ba84b0bfSMickaël Salaün 	};
93ba84b0bfSMickaël Salaün 
94ba84b0bfSMickaël Salaün 	env_path_name = getenv(env_var);
95ba84b0bfSMickaël Salaün 	if (!env_path_name) {
96ba84b0bfSMickaël Salaün 		/* Prevents users to forget a setting. */
97ba84b0bfSMickaël Salaün 		fprintf(stderr, "Missing environment variable %s\n", env_var);
98ba84b0bfSMickaël Salaün 		return 1;
99ba84b0bfSMickaël Salaün 	}
100ba84b0bfSMickaël Salaün 	env_path_name = strdup(env_path_name);
101ba84b0bfSMickaël Salaün 	unsetenv(env_var);
102ba84b0bfSMickaël Salaün 	num_paths = parse_path(env_path_name, &path_list);
103ba84b0bfSMickaël Salaün 	if (num_paths == 1 && path_list[0][0] == '\0') {
104ba84b0bfSMickaël Salaün 		/*
105ba84b0bfSMickaël Salaün 		 * Allows to not use all possible restrictions (e.g. use
106ba84b0bfSMickaël Salaün 		 * LL_FS_RO without LL_FS_RW).
107ba84b0bfSMickaël Salaün 		 */
108ba84b0bfSMickaël Salaün 		ret = 0;
109ba84b0bfSMickaël Salaün 		goto out_free_name;
110ba84b0bfSMickaël Salaün 	}
111ba84b0bfSMickaël Salaün 
112ba84b0bfSMickaël Salaün 	for (i = 0; i < num_paths; i++) {
113ba84b0bfSMickaël Salaün 		struct stat statbuf;
114ba84b0bfSMickaël Salaün 
11581709f3dSMickaël Salaün 		path_beneath.parent_fd = open(path_list[i], O_PATH | O_CLOEXEC);
116ba84b0bfSMickaël Salaün 		if (path_beneath.parent_fd < 0) {
117ba84b0bfSMickaël Salaün 			fprintf(stderr, "Failed to open \"%s\": %s\n",
11881709f3dSMickaël Salaün 				path_list[i], strerror(errno));
119ba84b0bfSMickaël Salaün 			goto out_free_name;
120ba84b0bfSMickaël Salaün 		}
121ba84b0bfSMickaël Salaün 		if (fstat(path_beneath.parent_fd, &statbuf)) {
122ba84b0bfSMickaël Salaün 			close(path_beneath.parent_fd);
123ba84b0bfSMickaël Salaün 			goto out_free_name;
124ba84b0bfSMickaël Salaün 		}
125ba84b0bfSMickaël Salaün 		path_beneath.allowed_access = allowed_access;
126ba84b0bfSMickaël Salaün 		if (!S_ISDIR(statbuf.st_mode))
127ba84b0bfSMickaël Salaün 			path_beneath.allowed_access &= ACCESS_FILE;
128ba84b0bfSMickaël Salaün 		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
129ba84b0bfSMickaël Salaün 				      &path_beneath, 0)) {
13081709f3dSMickaël Salaün 			fprintf(stderr,
13181709f3dSMickaël Salaün 				"Failed to update the ruleset with \"%s\": %s\n",
132ba84b0bfSMickaël Salaün 				path_list[i], strerror(errno));
133ba84b0bfSMickaël Salaün 			close(path_beneath.parent_fd);
134ba84b0bfSMickaël Salaün 			goto out_free_name;
135ba84b0bfSMickaël Salaün 		}
136ba84b0bfSMickaël Salaün 		close(path_beneath.parent_fd);
137ba84b0bfSMickaël Salaün 	}
138ba84b0bfSMickaël Salaün 	ret = 0;
139ba84b0bfSMickaël Salaün 
140ba84b0bfSMickaël Salaün out_free_name:
14166b513b7STom Rix 	free(path_list);
142ba84b0bfSMickaël Salaün 	free(env_path_name);
143ba84b0bfSMickaël Salaün 	return ret;
144ba84b0bfSMickaël Salaün }
145ba84b0bfSMickaël Salaün 
1469805a722SMickaël Salaün /* clang-format off */
1479805a722SMickaël Salaün 
148ba84b0bfSMickaël Salaün #define ACCESS_FS_ROUGHLY_READ ( \
149ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_EXECUTE | \
150ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_READ_FILE | \
151ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_READ_DIR)
152ba84b0bfSMickaël Salaün 
153ba84b0bfSMickaël Salaün #define ACCESS_FS_ROUGHLY_WRITE ( \
154ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_WRITE_FILE | \
155ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_REMOVE_DIR | \
156ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_REMOVE_FILE | \
157ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_CHAR | \
158ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_DIR | \
159ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_REG | \
160ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_SOCK | \
161ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_FIFO | \
162ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_BLOCK | \
16376b902f8SMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_SYM | \
164faeb9197SGünther Noack 	LANDLOCK_ACCESS_FS_REFER | \
165faeb9197SGünther Noack 	LANDLOCK_ACCESS_FS_TRUNCATE)
16676b902f8SMickaël Salaün 
1679805a722SMickaël Salaün /* clang-format on */
1689805a722SMickaël Salaün 
169faeb9197SGünther Noack #define LANDLOCK_ABI_LAST 3
170903cfe8aSMickaël Salaün 
main(const int argc,char * const argv[],char * const * const envp)171ba84b0bfSMickaël Salaün int main(const int argc, char *const argv[], char *const *const envp)
172ba84b0bfSMickaël Salaün {
173ba84b0bfSMickaël Salaün 	const char *cmd_path;
174ba84b0bfSMickaël Salaün 	char *const *cmd_argv;
17576b902f8SMickaël Salaün 	int ruleset_fd, abi;
17676b902f8SMickaël Salaün 	__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
17776b902f8SMickaël Salaün 	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
178ba84b0bfSMickaël Salaün 	struct landlock_ruleset_attr ruleset_attr = {
17976b902f8SMickaël Salaün 		.handled_access_fs = access_fs_rw,
180ba84b0bfSMickaël Salaün 	};
181ba84b0bfSMickaël Salaün 
182ba84b0bfSMickaël Salaün 	if (argc < 2) {
18381709f3dSMickaël Salaün 		fprintf(stderr,
18481709f3dSMickaël Salaün 			"usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n",
185ba84b0bfSMickaël Salaün 			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
18681709f3dSMickaël Salaün 		fprintf(stderr,
18781709f3dSMickaël Salaün 			"Launch a command in a restricted environment.\n\n");
188ba84b0bfSMickaël Salaün 		fprintf(stderr, "Environment variables containing paths, "
189ba84b0bfSMickaël Salaün 				"each separated by a colon:\n");
19081709f3dSMickaël Salaün 		fprintf(stderr,
19181709f3dSMickaël Salaün 			"* %s: list of paths allowed to be used in a read-only way.\n",
192ba84b0bfSMickaël Salaün 			ENV_FS_RO_NAME);
19381709f3dSMickaël Salaün 		fprintf(stderr,
19481709f3dSMickaël Salaün 			"* %s: list of paths allowed to be used in a read-write way.\n",
195ba84b0bfSMickaël Salaün 			ENV_FS_RW_NAME);
19681709f3dSMickaël Salaün 		fprintf(stderr,
19781709f3dSMickaël Salaün 			"\nexample:\n"
198ba84b0bfSMickaël Salaün 			"%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" "
199ba84b0bfSMickaël Salaün 			"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
200903cfe8aSMickaël Salaün 			"%s bash -i\n\n",
201ba84b0bfSMickaël Salaün 			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
202903cfe8aSMickaël Salaün 		fprintf(stderr,
203903cfe8aSMickaël Salaün 			"This sandboxer can use Landlock features "
204903cfe8aSMickaël Salaün 			"up to ABI version %d.\n",
205903cfe8aSMickaël Salaün 			LANDLOCK_ABI_LAST);
206ba84b0bfSMickaël Salaün 		return 1;
207ba84b0bfSMickaël Salaün 	}
208ba84b0bfSMickaël Salaün 
20976b902f8SMickaël Salaün 	abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
21076b902f8SMickaël Salaün 	if (abi < 0) {
211ba84b0bfSMickaël Salaün 		const int err = errno;
212ba84b0bfSMickaël Salaün 
21376b902f8SMickaël Salaün 		perror("Failed to check Landlock compatibility");
214ba84b0bfSMickaël Salaün 		switch (err) {
215ba84b0bfSMickaël Salaün 		case ENOSYS:
21681709f3dSMickaël Salaün 			fprintf(stderr,
21781709f3dSMickaël Salaün 				"Hint: Landlock is not supported by the current kernel. "
218ba84b0bfSMickaël Salaün 				"To support it, build the kernel with "
219ba84b0bfSMickaël Salaün 				"CONFIG_SECURITY_LANDLOCK=y and prepend "
220ba84b0bfSMickaël Salaün 				"\"landlock,\" to the content of CONFIG_LSM.\n");
221ba84b0bfSMickaël Salaün 			break;
222ba84b0bfSMickaël Salaün 		case EOPNOTSUPP:
22381709f3dSMickaël Salaün 			fprintf(stderr,
22481709f3dSMickaël Salaün 				"Hint: Landlock is currently disabled. "
225ba84b0bfSMickaël Salaün 				"It can be enabled in the kernel configuration by "
226ba84b0bfSMickaël Salaün 				"prepending \"landlock,\" to the content of CONFIG_LSM, "
227ba84b0bfSMickaël Salaün 				"or at boot time by setting the same content to the "
228ba84b0bfSMickaël Salaün 				"\"lsm\" kernel parameter.\n");
229ba84b0bfSMickaël Salaün 			break;
230ba84b0bfSMickaël Salaün 		}
231ba84b0bfSMickaël Salaün 		return 1;
232ba84b0bfSMickaël Salaün 	}
233903cfe8aSMickaël Salaün 
23476b902f8SMickaël Salaün 	/* Best-effort security. */
235903cfe8aSMickaël Salaün 	switch (abi) {
236903cfe8aSMickaël Salaün 	case 1:
237*f6e53fb2SGünther Noack 		/*
238*f6e53fb2SGünther Noack 		 * Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2
239*f6e53fb2SGünther Noack 		 *
240*f6e53fb2SGünther Noack 		 * Note: The "refer" operations (file renaming and linking
241*f6e53fb2SGünther Noack 		 * across different directories) are always forbidden when using
242*f6e53fb2SGünther Noack 		 * Landlock with ABI 1.
243*f6e53fb2SGünther Noack 		 *
244*f6e53fb2SGünther Noack 		 * If only ABI 1 is available, this sandboxer knowingly forbids
245*f6e53fb2SGünther Noack 		 * refer operations.
246*f6e53fb2SGünther Noack 		 *
247*f6e53fb2SGünther Noack 		 * If a program *needs* to do refer operations after enabling
248*f6e53fb2SGünther Noack 		 * Landlock, it can not use Landlock at ABI level 1.  To be
249*f6e53fb2SGünther Noack 		 * compatible with different kernel versions, such programs
250*f6e53fb2SGünther Noack 		 * should then fall back to not restrict themselves at all if
251*f6e53fb2SGünther Noack 		 * the running kernel only supports ABI 1.
252*f6e53fb2SGünther Noack 		 */
253903cfe8aSMickaël Salaün 		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER;
254faeb9197SGünther Noack 		__attribute__((fallthrough));
255faeb9197SGünther Noack 	case 2:
256faeb9197SGünther Noack 		/* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
257faeb9197SGünther Noack 		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
258903cfe8aSMickaël Salaün 
259903cfe8aSMickaël Salaün 		fprintf(stderr,
260903cfe8aSMickaël Salaün 			"Hint: You should update the running kernel "
261903cfe8aSMickaël Salaün 			"to leverage Landlock features "
262903cfe8aSMickaël Salaün 			"provided by ABI version %d (instead of %d).\n",
263903cfe8aSMickaël Salaün 			LANDLOCK_ABI_LAST, abi);
264903cfe8aSMickaël Salaün 		__attribute__((fallthrough));
265903cfe8aSMickaël Salaün 	case LANDLOCK_ABI_LAST:
266903cfe8aSMickaël Salaün 		break;
267903cfe8aSMickaël Salaün 	default:
268903cfe8aSMickaël Salaün 		fprintf(stderr,
269903cfe8aSMickaël Salaün 			"Hint: You should update this sandboxer "
270903cfe8aSMickaël Salaün 			"to leverage Landlock features "
271903cfe8aSMickaël Salaün 			"provided by ABI version %d (instead of %d).\n",
272903cfe8aSMickaël Salaün 			abi, LANDLOCK_ABI_LAST);
27376b902f8SMickaël Salaün 	}
274903cfe8aSMickaël Salaün 	access_fs_ro &= ruleset_attr.handled_access_fs;
275903cfe8aSMickaël Salaün 	access_fs_rw &= ruleset_attr.handled_access_fs;
27676b902f8SMickaël Salaün 
27776b902f8SMickaël Salaün 	ruleset_fd =
27876b902f8SMickaël Salaün 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
27976b902f8SMickaël Salaün 	if (ruleset_fd < 0) {
28076b902f8SMickaël Salaün 		perror("Failed to create a ruleset");
28176b902f8SMickaël Salaün 		return 1;
28276b902f8SMickaël Salaün 	}
28376b902f8SMickaël Salaün 	if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
284ba84b0bfSMickaël Salaün 		goto err_close_ruleset;
285ba84b0bfSMickaël Salaün 	}
28676b902f8SMickaël Salaün 	if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
287ba84b0bfSMickaël Salaün 		goto err_close_ruleset;
288ba84b0bfSMickaël Salaün 	}
289ba84b0bfSMickaël Salaün 	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
290ba84b0bfSMickaël Salaün 		perror("Failed to restrict privileges");
291ba84b0bfSMickaël Salaün 		goto err_close_ruleset;
292ba84b0bfSMickaël Salaün 	}
293ba84b0bfSMickaël Salaün 	if (landlock_restrict_self(ruleset_fd, 0)) {
294ba84b0bfSMickaël Salaün 		perror("Failed to enforce ruleset");
295ba84b0bfSMickaël Salaün 		goto err_close_ruleset;
296ba84b0bfSMickaël Salaün 	}
297ba84b0bfSMickaël Salaün 	close(ruleset_fd);
298ba84b0bfSMickaël Salaün 
299ba84b0bfSMickaël Salaün 	cmd_path = argv[1];
300ba84b0bfSMickaël Salaün 	cmd_argv = argv + 1;
301ba84b0bfSMickaël Salaün 	execvpe(cmd_path, cmd_argv, envp);
302ba84b0bfSMickaël Salaün 	fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path,
303ba84b0bfSMickaël Salaün 		strerror(errno));
304ba84b0bfSMickaël Salaün 	fprintf(stderr, "Hint: access to the binary, the interpreter or "
305ba84b0bfSMickaël Salaün 			"shared libraries may be denied.\n");
306ba84b0bfSMickaël Salaün 	return 1;
307ba84b0bfSMickaël Salaün 
308ba84b0bfSMickaël Salaün err_close_ruleset:
309ba84b0bfSMickaël Salaün 	close(ruleset_fd);
310ba84b0bfSMickaël Salaün 	return 1;
311ba84b0bfSMickaël Salaün }
312