condition.c revision 2ca9bf453bdd478bcb6c01aa2d0bd4c2f4350563
1/*
2 * security/tomoyo/condition.c
3 *
4 * Copyright (C) 2005-2011  NTT DATA CORPORATION
5 */
6
7#include "common.h"
8#include <linux/slab.h>
9
10/* List of "struct tomoyo_condition". */
11LIST_HEAD(tomoyo_condition_list);
12
13/**
14 * tomoyo_scan_exec_realpath - Check "exec.realpath" parameter of "struct tomoyo_condition".
15 *
16 * @file:  Pointer to "struct file".
17 * @ptr:   Pointer to "struct tomoyo_name_union".
18 * @match: True if "exec.realpath=", false if "exec.realpath!=".
19 *
20 * Returns true on success, false otherwise.
21 */
22static bool tomoyo_scan_exec_realpath(struct file *file,
23				      const struct tomoyo_name_union *ptr,
24				      const bool match)
25{
26	bool result;
27	struct tomoyo_path_info exe;
28	if (!file)
29		return false;
30	exe.name = tomoyo_realpath_from_path(&file->f_path);
31	if (!exe.name)
32		return false;
33	tomoyo_fill_path_info(&exe);
34	result = tomoyo_compare_name_union(&exe, ptr);
35	kfree(exe.name);
36	return result == match;
37}
38
39/**
40 * tomoyo_get_dqword - tomoyo_get_name() for a quoted string.
41 *
42 * @start: String to save.
43 *
44 * Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise.
45 */
46static const struct tomoyo_path_info *tomoyo_get_dqword(char *start)
47{
48	char *cp = start + strlen(start) - 1;
49	if (cp == start || *start++ != '"' || *cp != '"')
50		return NULL;
51	*cp = '\0';
52	if (*start && !tomoyo_correct_word(start))
53		return NULL;
54	return tomoyo_get_name(start);
55}
56
57/**
58 * tomoyo_parse_name_union_quoted - Parse a quoted word.
59 *
60 * @param: Pointer to "struct tomoyo_acl_param".
61 * @ptr:   Pointer to "struct tomoyo_name_union".
62 *
63 * Returns true on success, false otherwise.
64 */
65static bool tomoyo_parse_name_union_quoted(struct tomoyo_acl_param *param,
66					   struct tomoyo_name_union *ptr)
67{
68	char *filename = param->data;
69	if (*filename == '@')
70		return tomoyo_parse_name_union(param, ptr);
71	ptr->filename = tomoyo_get_dqword(filename);
72	return ptr->filename != NULL;
73}
74
75/**
76 * tomoyo_same_condition - Check for duplicated "struct tomoyo_condition" entry.
77 *
78 * @a: Pointer to "struct tomoyo_condition".
79 * @b: Pointer to "struct tomoyo_condition".
80 *
81 * Returns true if @a == @b, false otherwise.
82 */
83static inline bool tomoyo_same_condition(const struct tomoyo_condition *a,
84					 const struct tomoyo_condition *b)
85{
86	return a->size == b->size && a->condc == b->condc &&
87		a->numbers_count == b->numbers_count &&
88		a->names_count == b->names_count &&
89		!memcmp(a + 1, b + 1, a->size - sizeof(*a));
90}
91
92/**
93 * tomoyo_condition_type - Get condition type.
94 *
95 * @word: Keyword string.
96 *
97 * Returns one of values in "enum tomoyo_conditions_index" on success,
98 * TOMOYO_MAX_CONDITION_KEYWORD otherwise.
99 */
100static u8 tomoyo_condition_type(const char *word)
101{
102	u8 i;
103	for (i = 0; i < TOMOYO_MAX_CONDITION_KEYWORD; i++) {
104		if (!strcmp(word, tomoyo_condition_keyword[i]))
105			break;
106	}
107	return i;
108}
109
110/* Define this to enable debug mode. */
111/* #define DEBUG_CONDITION */
112
113#ifdef DEBUG_CONDITION
114#define dprintk printk
115#else
116#define dprintk(...) do { } while (0)
117#endif
118
119/**
120 * tomoyo_commit_condition - Commit "struct tomoyo_condition".
121 *
122 * @entry: Pointer to "struct tomoyo_condition".
123 *
124 * Returns pointer to "struct tomoyo_condition" on success, NULL otherwise.
125 *
126 * This function merges duplicated entries. This function returns NULL if
127 * @entry is not duplicated but memory quota for policy has exceeded.
128 */
129static struct tomoyo_condition *tomoyo_commit_condition
130(struct tomoyo_condition *entry)
131{
132	struct tomoyo_condition *ptr;
133	bool found = false;
134	if (mutex_lock_interruptible(&tomoyo_policy_lock)) {
135		dprintk(KERN_WARNING "%u: %s failed\n", __LINE__, __func__);
136		ptr = NULL;
137		found = true;
138		goto out;
139	}
140	list_for_each_entry_rcu(ptr, &tomoyo_condition_list, head.list) {
141		if (!tomoyo_same_condition(ptr, entry))
142			continue;
143		/* Same entry found. Share this entry. */
144		atomic_inc(&ptr->head.users);
145		found = true;
146		break;
147	}
148	if (!found) {
149		if (tomoyo_memory_ok(entry)) {
150			atomic_set(&entry->head.users, 1);
151			list_add_rcu(&entry->head.list,
152				     &tomoyo_condition_list);
153		} else {
154			found = true;
155			ptr = NULL;
156		}
157	}
158	mutex_unlock(&tomoyo_policy_lock);
159out:
160	if (found) {
161		tomoyo_del_condition(&entry->head.list);
162		kfree(entry);
163		entry = ptr;
164	}
165	return entry;
166}
167
168/**
169 * tomoyo_get_condition - Parse condition part.
170 *
171 * @param: Pointer to "struct tomoyo_acl_param".
172 *
173 * Returns pointer to "struct tomoyo_condition" on success, NULL otherwise.
174 */
175struct tomoyo_condition *tomoyo_get_condition(struct tomoyo_acl_param *param)
176{
177	struct tomoyo_condition *entry = NULL;
178	struct tomoyo_condition_element *condp = NULL;
179	struct tomoyo_number_union *numbers_p = NULL;
180	struct tomoyo_name_union *names_p = NULL;
181	struct tomoyo_condition e = { };
182	char * const start_of_string = param->data;
183	char * const end_of_string = start_of_string + strlen(start_of_string);
184	char *pos;
185rerun:
186	pos = start_of_string;
187	while (1) {
188		u8 left = -1;
189		u8 right = -1;
190		char *left_word = pos;
191		char *cp;
192		char *right_word;
193		bool is_not;
194		if (!*left_word)
195			break;
196		/*
197		 * Since left-hand condition does not allow use of "path_group"
198		 * or "number_group" and environment variable's names do not
199		 * accept '=', it is guaranteed that the original line consists
200		 * of one or more repetition of $left$operator$right blocks
201		 * where "$left is free from '=' and ' '" and "$operator is
202		 * either '=' or '!='" and "$right is free from ' '".
203		 * Therefore, we can reconstruct the original line at the end
204		 * of dry run even if we overwrite $operator with '\0'.
205		 */
206		cp = strchr(pos, ' ');
207		if (cp) {
208			*cp = '\0'; /* Will restore later. */
209			pos = cp + 1;
210		} else {
211			pos = "";
212		}
213		right_word = strchr(left_word, '=');
214		if (!right_word || right_word == left_word)
215			goto out;
216		is_not = *(right_word - 1) == '!';
217		if (is_not)
218			*(right_word++ - 1) = '\0'; /* Will restore later. */
219		else if (*(right_word + 1) != '=')
220			*right_word++ = '\0'; /* Will restore later. */
221		else
222			goto out;
223		dprintk(KERN_WARNING "%u: <%s>%s=<%s>\n", __LINE__, left_word,
224			is_not ? "!" : "", right_word);
225		left = tomoyo_condition_type(left_word);
226		dprintk(KERN_WARNING "%u: <%s> left=%u\n", __LINE__, left_word,
227			left);
228		if (left == TOMOYO_MAX_CONDITION_KEYWORD) {
229			if (!numbers_p) {
230				e.numbers_count++;
231			} else {
232				e.numbers_count--;
233				left = TOMOYO_NUMBER_UNION;
234				param->data = left_word;
235				if (*left_word == '@' ||
236				    !tomoyo_parse_number_union(param,
237							       numbers_p++))
238					goto out;
239			}
240		}
241		if (!condp)
242			e.condc++;
243		else
244			e.condc--;
245		if (left == TOMOYO_EXEC_REALPATH ||
246		    left == TOMOYO_SYMLINK_TARGET) {
247			if (!names_p) {
248				e.names_count++;
249			} else {
250				e.names_count--;
251				right = TOMOYO_NAME_UNION;
252				param->data = right_word;
253				if (!tomoyo_parse_name_union_quoted(param,
254								    names_p++))
255					goto out;
256			}
257			goto store_value;
258		}
259		right = tomoyo_condition_type(right_word);
260		if (right == TOMOYO_MAX_CONDITION_KEYWORD) {
261			if (!numbers_p) {
262				e.numbers_count++;
263			} else {
264				e.numbers_count--;
265				right = TOMOYO_NUMBER_UNION;
266				param->data = right_word;
267				if (!tomoyo_parse_number_union(param,
268							       numbers_p++))
269					goto out;
270			}
271		}
272store_value:
273		if (!condp) {
274			dprintk(KERN_WARNING "%u: dry_run left=%u right=%u "
275				"match=%u\n", __LINE__, left, right, !is_not);
276			continue;
277		}
278		condp->left = left;
279		condp->right = right;
280		condp->equals = !is_not;
281		dprintk(KERN_WARNING "%u: left=%u right=%u match=%u\n",
282			__LINE__, condp->left, condp->right,
283			condp->equals);
284		condp++;
285	}
286	dprintk(KERN_INFO "%u: cond=%u numbers=%u names=%u\n",
287		__LINE__, e.condc, e.numbers_count, e.names_count);
288	if (entry) {
289		BUG_ON(e.names_count | e.numbers_count | e.condc);
290		return tomoyo_commit_condition(entry);
291	}
292	e.size = sizeof(*entry)
293		+ e.condc * sizeof(struct tomoyo_condition_element)
294		+ e.numbers_count * sizeof(struct tomoyo_number_union)
295		+ e.names_count * sizeof(struct tomoyo_name_union);
296	entry = kzalloc(e.size, GFP_NOFS);
297	if (!entry)
298		return NULL;
299	*entry = e;
300	condp = (struct tomoyo_condition_element *) (entry + 1);
301	numbers_p = (struct tomoyo_number_union *) (condp + e.condc);
302	names_p = (struct tomoyo_name_union *) (numbers_p + e.numbers_count);
303	{
304		bool flag = false;
305		for (pos = start_of_string; pos < end_of_string; pos++) {
306			if (*pos)
307				continue;
308			if (flag) /* Restore " ". */
309				*pos = ' ';
310			else if (*(pos + 1) == '=') /* Restore "!=". */
311				*pos = '!';
312			else /* Restore "=". */
313				*pos = '=';
314			flag = !flag;
315		}
316	}
317	goto rerun;
318out:
319	dprintk(KERN_WARNING "%u: %s failed\n", __LINE__, __func__);
320	if (entry) {
321		tomoyo_del_condition(&entry->head.list);
322		kfree(entry);
323	}
324	return NULL;
325}
326
327/**
328 * tomoyo_get_attributes - Revalidate "struct inode".
329 *
330 * @obj: Pointer to "struct tomoyo_obj_info".
331 *
332 * Returns nothing.
333 */
334void tomoyo_get_attributes(struct tomoyo_obj_info *obj)
335{
336	u8 i;
337	struct dentry *dentry = NULL;
338
339	for (i = 0; i < TOMOYO_MAX_PATH_STAT; i++) {
340		struct inode *inode;
341		switch (i) {
342		case TOMOYO_PATH1:
343			dentry = obj->path1.dentry;
344			if (!dentry)
345				continue;
346			break;
347		case TOMOYO_PATH2:
348			dentry = obj->path2.dentry;
349			if (!dentry)
350				continue;
351			break;
352		default:
353			if (!dentry)
354				continue;
355			dentry = dget_parent(dentry);
356			break;
357		}
358		inode = dentry->d_inode;
359		if (inode) {
360			struct tomoyo_mini_stat *stat = &obj->stat[i];
361			stat->uid  = inode->i_uid;
362			stat->gid  = inode->i_gid;
363			stat->ino  = inode->i_ino;
364			stat->mode = inode->i_mode;
365			stat->dev  = inode->i_sb->s_dev;
366			stat->rdev = inode->i_rdev;
367			obj->stat_valid[i] = true;
368		}
369		if (i & 1) /* i == TOMOYO_PATH1_PARENT ||
370			      i == TOMOYO_PATH2_PARENT */
371			dput(dentry);
372	}
373}
374
375/**
376 * tomoyo_condition - Check condition part.
377 *
378 * @r:    Pointer to "struct tomoyo_request_info".
379 * @cond: Pointer to "struct tomoyo_condition". Maybe NULL.
380 *
381 * Returns true on success, false otherwise.
382 *
383 * Caller holds tomoyo_read_lock().
384 */
385bool tomoyo_condition(struct tomoyo_request_info *r,
386		      const struct tomoyo_condition *cond)
387{
388	u32 i;
389	unsigned long min_v[2] = { 0, 0 };
390	unsigned long max_v[2] = { 0, 0 };
391	const struct tomoyo_condition_element *condp;
392	const struct tomoyo_number_union *numbers_p;
393	const struct tomoyo_name_union *names_p;
394	struct tomoyo_obj_info *obj;
395	u16 condc;
396	if (!cond)
397		return true;
398	condc = cond->condc;
399	obj = r->obj;
400	condp = (struct tomoyo_condition_element *) (cond + 1);
401	numbers_p = (const struct tomoyo_number_union *) (condp + condc);
402	names_p = (const struct tomoyo_name_union *)
403		(numbers_p + cond->numbers_count);
404	for (i = 0; i < condc; i++) {
405		const bool match = condp->equals;
406		const u8 left = condp->left;
407		const u8 right = condp->right;
408		bool is_bitop[2] = { false, false };
409		u8 j;
410		condp++;
411		/* Check string expressions. */
412		if (right == TOMOYO_NAME_UNION) {
413			const struct tomoyo_name_union *ptr = names_p++;
414			switch (left) {
415				struct tomoyo_path_info *symlink;
416				struct tomoyo_execve *ee;
417				struct file *file;
418			case TOMOYO_SYMLINK_TARGET:
419				symlink = obj ? obj->symlink_target : NULL;
420				if (!symlink ||
421				    !tomoyo_compare_name_union(symlink, ptr)
422				    == match)
423					goto out;
424				break;
425			case TOMOYO_EXEC_REALPATH:
426				ee = r->ee;
427				file = ee ? ee->bprm->file : NULL;
428				if (!tomoyo_scan_exec_realpath(file, ptr,
429							       match))
430					goto out;
431				break;
432			}
433			continue;
434		}
435		/* Check numeric or bit-op expressions. */
436		for (j = 0; j < 2; j++) {
437			const u8 index = j ? right : left;
438			unsigned long value = 0;
439			switch (index) {
440			case TOMOYO_TASK_UID:
441				value = current_uid();
442				break;
443			case TOMOYO_TASK_EUID:
444				value = current_euid();
445				break;
446			case TOMOYO_TASK_SUID:
447				value = current_suid();
448				break;
449			case TOMOYO_TASK_FSUID:
450				value = current_fsuid();
451				break;
452			case TOMOYO_TASK_GID:
453				value = current_gid();
454				break;
455			case TOMOYO_TASK_EGID:
456				value = current_egid();
457				break;
458			case TOMOYO_TASK_SGID:
459				value = current_sgid();
460				break;
461			case TOMOYO_TASK_FSGID:
462				value = current_fsgid();
463				break;
464			case TOMOYO_TASK_PID:
465				value = tomoyo_sys_getpid();
466				break;
467			case TOMOYO_TASK_PPID:
468				value = tomoyo_sys_getppid();
469				break;
470			case TOMOYO_TYPE_IS_SOCKET:
471				value = S_IFSOCK;
472				break;
473			case TOMOYO_TYPE_IS_SYMLINK:
474				value = S_IFLNK;
475				break;
476			case TOMOYO_TYPE_IS_FILE:
477				value = S_IFREG;
478				break;
479			case TOMOYO_TYPE_IS_BLOCK_DEV:
480				value = S_IFBLK;
481				break;
482			case TOMOYO_TYPE_IS_DIRECTORY:
483				value = S_IFDIR;
484				break;
485			case TOMOYO_TYPE_IS_CHAR_DEV:
486				value = S_IFCHR;
487				break;
488			case TOMOYO_TYPE_IS_FIFO:
489				value = S_IFIFO;
490				break;
491			case TOMOYO_MODE_SETUID:
492				value = S_ISUID;
493				break;
494			case TOMOYO_MODE_SETGID:
495				value = S_ISGID;
496				break;
497			case TOMOYO_MODE_STICKY:
498				value = S_ISVTX;
499				break;
500			case TOMOYO_MODE_OWNER_READ:
501				value = S_IRUSR;
502				break;
503			case TOMOYO_MODE_OWNER_WRITE:
504				value = S_IWUSR;
505				break;
506			case TOMOYO_MODE_OWNER_EXECUTE:
507				value = S_IXUSR;
508				break;
509			case TOMOYO_MODE_GROUP_READ:
510				value = S_IRGRP;
511				break;
512			case TOMOYO_MODE_GROUP_WRITE:
513				value = S_IWGRP;
514				break;
515			case TOMOYO_MODE_GROUP_EXECUTE:
516				value = S_IXGRP;
517				break;
518			case TOMOYO_MODE_OTHERS_READ:
519				value = S_IROTH;
520				break;
521			case TOMOYO_MODE_OTHERS_WRITE:
522				value = S_IWOTH;
523				break;
524			case TOMOYO_MODE_OTHERS_EXECUTE:
525				value = S_IXOTH;
526				break;
527			case TOMOYO_NUMBER_UNION:
528				/* Fetch values later. */
529				break;
530			default:
531				if (!obj)
532					goto out;
533				if (!obj->validate_done) {
534					tomoyo_get_attributes(obj);
535					obj->validate_done = true;
536				}
537				{
538					u8 stat_index;
539					struct tomoyo_mini_stat *stat;
540					switch (index) {
541					case TOMOYO_PATH1_UID:
542					case TOMOYO_PATH1_GID:
543					case TOMOYO_PATH1_INO:
544					case TOMOYO_PATH1_MAJOR:
545					case TOMOYO_PATH1_MINOR:
546					case TOMOYO_PATH1_TYPE:
547					case TOMOYO_PATH1_DEV_MAJOR:
548					case TOMOYO_PATH1_DEV_MINOR:
549					case TOMOYO_PATH1_PERM:
550						stat_index = TOMOYO_PATH1;
551						break;
552					case TOMOYO_PATH2_UID:
553					case TOMOYO_PATH2_GID:
554					case TOMOYO_PATH2_INO:
555					case TOMOYO_PATH2_MAJOR:
556					case TOMOYO_PATH2_MINOR:
557					case TOMOYO_PATH2_TYPE:
558					case TOMOYO_PATH2_DEV_MAJOR:
559					case TOMOYO_PATH2_DEV_MINOR:
560					case TOMOYO_PATH2_PERM:
561						stat_index = TOMOYO_PATH2;
562						break;
563					case TOMOYO_PATH1_PARENT_UID:
564					case TOMOYO_PATH1_PARENT_GID:
565					case TOMOYO_PATH1_PARENT_INO:
566					case TOMOYO_PATH1_PARENT_PERM:
567						stat_index =
568							TOMOYO_PATH1_PARENT;
569						break;
570					case TOMOYO_PATH2_PARENT_UID:
571					case TOMOYO_PATH2_PARENT_GID:
572					case TOMOYO_PATH2_PARENT_INO:
573					case TOMOYO_PATH2_PARENT_PERM:
574						stat_index =
575							TOMOYO_PATH2_PARENT;
576						break;
577					default:
578						goto out;
579					}
580					if (!obj->stat_valid[stat_index])
581						goto out;
582					stat = &obj->stat[stat_index];
583					switch (index) {
584					case TOMOYO_PATH1_UID:
585					case TOMOYO_PATH2_UID:
586					case TOMOYO_PATH1_PARENT_UID:
587					case TOMOYO_PATH2_PARENT_UID:
588						value = stat->uid;
589						break;
590					case TOMOYO_PATH1_GID:
591					case TOMOYO_PATH2_GID:
592					case TOMOYO_PATH1_PARENT_GID:
593					case TOMOYO_PATH2_PARENT_GID:
594						value = stat->gid;
595						break;
596					case TOMOYO_PATH1_INO:
597					case TOMOYO_PATH2_INO:
598					case TOMOYO_PATH1_PARENT_INO:
599					case TOMOYO_PATH2_PARENT_INO:
600						value = stat->ino;
601						break;
602					case TOMOYO_PATH1_MAJOR:
603					case TOMOYO_PATH2_MAJOR:
604						value = MAJOR(stat->dev);
605						break;
606					case TOMOYO_PATH1_MINOR:
607					case TOMOYO_PATH2_MINOR:
608						value = MINOR(stat->dev);
609						break;
610					case TOMOYO_PATH1_TYPE:
611					case TOMOYO_PATH2_TYPE:
612						value = stat->mode & S_IFMT;
613						break;
614					case TOMOYO_PATH1_DEV_MAJOR:
615					case TOMOYO_PATH2_DEV_MAJOR:
616						value = MAJOR(stat->rdev);
617						break;
618					case TOMOYO_PATH1_DEV_MINOR:
619					case TOMOYO_PATH2_DEV_MINOR:
620						value = MINOR(stat->rdev);
621						break;
622					case TOMOYO_PATH1_PERM:
623					case TOMOYO_PATH2_PERM:
624					case TOMOYO_PATH1_PARENT_PERM:
625					case TOMOYO_PATH2_PARENT_PERM:
626						value = stat->mode & S_IALLUGO;
627						break;
628					}
629				}
630				break;
631			}
632			max_v[j] = value;
633			min_v[j] = value;
634			switch (index) {
635			case TOMOYO_MODE_SETUID:
636			case TOMOYO_MODE_SETGID:
637			case TOMOYO_MODE_STICKY:
638			case TOMOYO_MODE_OWNER_READ:
639			case TOMOYO_MODE_OWNER_WRITE:
640			case TOMOYO_MODE_OWNER_EXECUTE:
641			case TOMOYO_MODE_GROUP_READ:
642			case TOMOYO_MODE_GROUP_WRITE:
643			case TOMOYO_MODE_GROUP_EXECUTE:
644			case TOMOYO_MODE_OTHERS_READ:
645			case TOMOYO_MODE_OTHERS_WRITE:
646			case TOMOYO_MODE_OTHERS_EXECUTE:
647				is_bitop[j] = true;
648			}
649		}
650		if (left == TOMOYO_NUMBER_UNION) {
651			/* Fetch values now. */
652			const struct tomoyo_number_union *ptr = numbers_p++;
653			min_v[0] = ptr->values[0];
654			max_v[0] = ptr->values[1];
655		}
656		if (right == TOMOYO_NUMBER_UNION) {
657			/* Fetch values now. */
658			const struct tomoyo_number_union *ptr = numbers_p++;
659			if (ptr->group) {
660				if (tomoyo_number_matches_group(min_v[0],
661								max_v[0],
662								ptr->group)
663				    == match)
664					continue;
665			} else {
666				if ((min_v[0] <= ptr->values[1] &&
667				     max_v[0] >= ptr->values[0]) == match)
668					continue;
669			}
670			goto out;
671		}
672		/*
673		 * Bit operation is valid only when counterpart value
674		 * represents permission.
675		 */
676		if (is_bitop[0] && is_bitop[1]) {
677			goto out;
678		} else if (is_bitop[0]) {
679			switch (right) {
680			case TOMOYO_PATH1_PERM:
681			case TOMOYO_PATH1_PARENT_PERM:
682			case TOMOYO_PATH2_PERM:
683			case TOMOYO_PATH2_PARENT_PERM:
684				if (!(max_v[0] & max_v[1]) == !match)
685					continue;
686			}
687			goto out;
688		} else if (is_bitop[1]) {
689			switch (left) {
690			case TOMOYO_PATH1_PERM:
691			case TOMOYO_PATH1_PARENT_PERM:
692			case TOMOYO_PATH2_PERM:
693			case TOMOYO_PATH2_PARENT_PERM:
694				if (!(max_v[0] & max_v[1]) == !match)
695					continue;
696			}
697			goto out;
698		}
699		/* Normal value range comparison. */
700		if ((min_v[0] <= max_v[1] && max_v[0] >= min_v[1]) == match)
701			continue;
702out:
703		return false;
704	}
705	return true;
706}
707