Merge branch 'akpm' (fixes from Andrew)
[linux-drm-fsl-dcu.git] / security / device_cgroup.c
1 /*
2  * device_cgroup.c - device cgroup subsystem
3  *
4  * Copyright 2007 IBM Corp
5  */
6
7 #include <linux/device_cgroup.h>
8 #include <linux/cgroup.h>
9 #include <linux/ctype.h>
10 #include <linux/list.h>
11 #include <linux/uaccess.h>
12 #include <linux/seq_file.h>
13 #include <linux/slab.h>
14 #include <linux/rcupdate.h>
15 #include <linux/mutex.h>
16
17 #define ACC_MKNOD 1
18 #define ACC_READ  2
19 #define ACC_WRITE 4
20 #define ACC_MASK (ACC_MKNOD | ACC_READ | ACC_WRITE)
21
22 #define DEV_BLOCK 1
23 #define DEV_CHAR  2
24 #define DEV_ALL   4  /* this represents all devices */
25
26 static DEFINE_MUTEX(devcgroup_mutex);
27
28 enum devcg_behavior {
29         DEVCG_DEFAULT_NONE,
30         DEVCG_DEFAULT_ALLOW,
31         DEVCG_DEFAULT_DENY,
32 };
33
34 /*
35  * exception list locking rules:
36  * hold devcgroup_mutex for update/read.
37  * hold rcu_read_lock() for read.
38  */
39
40 struct dev_exception_item {
41         u32 major, minor;
42         short type;
43         short access;
44         struct list_head list;
45         struct rcu_head rcu;
46 };
47
48 struct dev_cgroup {
49         struct cgroup_subsys_state css;
50         struct list_head exceptions;
51         enum devcg_behavior behavior;
52 };
53
54 static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s)
55 {
56         return s ? container_of(s, struct dev_cgroup, css) : NULL;
57 }
58
59 static inline struct dev_cgroup *task_devcgroup(struct task_struct *task)
60 {
61         return css_to_devcgroup(task_css(task, devices_subsys_id));
62 }
63
64 struct cgroup_subsys devices_subsys;
65
66 /*
67  * called under devcgroup_mutex
68  */
69 static int dev_exceptions_copy(struct list_head *dest, struct list_head *orig)
70 {
71         struct dev_exception_item *ex, *tmp, *new;
72
73         lockdep_assert_held(&devcgroup_mutex);
74
75         list_for_each_entry(ex, orig, list) {
76                 new = kmemdup(ex, sizeof(*ex), GFP_KERNEL);
77                 if (!new)
78                         goto free_and_exit;
79                 list_add_tail(&new->list, dest);
80         }
81
82         return 0;
83
84 free_and_exit:
85         list_for_each_entry_safe(ex, tmp, dest, list) {
86                 list_del(&ex->list);
87                 kfree(ex);
88         }
89         return -ENOMEM;
90 }
91
92 /*
93  * called under devcgroup_mutex
94  */
95 static int dev_exception_add(struct dev_cgroup *dev_cgroup,
96                              struct dev_exception_item *ex)
97 {
98         struct dev_exception_item *excopy, *walk;
99
100         lockdep_assert_held(&devcgroup_mutex);
101
102         excopy = kmemdup(ex, sizeof(*ex), GFP_KERNEL);
103         if (!excopy)
104                 return -ENOMEM;
105
106         list_for_each_entry(walk, &dev_cgroup->exceptions, list) {
107                 if (walk->type != ex->type)
108                         continue;
109                 if (walk->major != ex->major)
110                         continue;
111                 if (walk->minor != ex->minor)
112                         continue;
113
114                 walk->access |= ex->access;
115                 kfree(excopy);
116                 excopy = NULL;
117         }
118
119         if (excopy != NULL)
120                 list_add_tail_rcu(&excopy->list, &dev_cgroup->exceptions);
121         return 0;
122 }
123
124 /*
125  * called under devcgroup_mutex
126  */
127 static void dev_exception_rm(struct dev_cgroup *dev_cgroup,
128                              struct dev_exception_item *ex)
129 {
130         struct dev_exception_item *walk, *tmp;
131
132         lockdep_assert_held(&devcgroup_mutex);
133
134         list_for_each_entry_safe(walk, tmp, &dev_cgroup->exceptions, list) {
135                 if (walk->type != ex->type)
136                         continue;
137                 if (walk->major != ex->major)
138                         continue;
139                 if (walk->minor != ex->minor)
140                         continue;
141
142                 walk->access &= ~ex->access;
143                 if (!walk->access) {
144                         list_del_rcu(&walk->list);
145                         kfree_rcu(walk, rcu);
146                 }
147         }
148 }
149
150 static void __dev_exception_clean(struct dev_cgroup *dev_cgroup)
151 {
152         struct dev_exception_item *ex, *tmp;
153
154         list_for_each_entry_safe(ex, tmp, &dev_cgroup->exceptions, list) {
155                 list_del_rcu(&ex->list);
156                 kfree_rcu(ex, rcu);
157         }
158 }
159
160 /**
161  * dev_exception_clean - frees all entries of the exception list
162  * @dev_cgroup: dev_cgroup with the exception list to be cleaned
163  *
164  * called under devcgroup_mutex
165  */
166 static void dev_exception_clean(struct dev_cgroup *dev_cgroup)
167 {
168         lockdep_assert_held(&devcgroup_mutex);
169
170         __dev_exception_clean(dev_cgroup);
171 }
172
173 static inline bool is_devcg_online(const struct dev_cgroup *devcg)
174 {
175         return (devcg->behavior != DEVCG_DEFAULT_NONE);
176 }
177
178 /**
179  * devcgroup_online - initializes devcgroup's behavior and exceptions based on
180  *                    parent's
181  * @css: css getting online
182  * returns 0 in case of success, error code otherwise
183  */
184 static int devcgroup_online(struct cgroup_subsys_state *css)
185 {
186         struct dev_cgroup *dev_cgroup = css_to_devcgroup(css);
187         struct dev_cgroup *parent_dev_cgroup = css_to_devcgroup(css_parent(css));
188         int ret = 0;
189
190         mutex_lock(&devcgroup_mutex);
191
192         if (parent_dev_cgroup == NULL)
193                 dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW;
194         else {
195                 ret = dev_exceptions_copy(&dev_cgroup->exceptions,
196                                           &parent_dev_cgroup->exceptions);
197                 if (!ret)
198                         dev_cgroup->behavior = parent_dev_cgroup->behavior;
199         }
200         mutex_unlock(&devcgroup_mutex);
201
202         return ret;
203 }
204
205 static void devcgroup_offline(struct cgroup_subsys_state *css)
206 {
207         struct dev_cgroup *dev_cgroup = css_to_devcgroup(css);
208
209         mutex_lock(&devcgroup_mutex);
210         dev_cgroup->behavior = DEVCG_DEFAULT_NONE;
211         mutex_unlock(&devcgroup_mutex);
212 }
213
214 /*
215  * called from kernel/cgroup.c with cgroup_lock() held.
216  */
217 static struct cgroup_subsys_state *
218 devcgroup_css_alloc(struct cgroup_subsys_state *parent_css)
219 {
220         struct dev_cgroup *dev_cgroup;
221
222         dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL);
223         if (!dev_cgroup)
224                 return ERR_PTR(-ENOMEM);
225         INIT_LIST_HEAD(&dev_cgroup->exceptions);
226         dev_cgroup->behavior = DEVCG_DEFAULT_NONE;
227
228         return &dev_cgroup->css;
229 }
230
231 static void devcgroup_css_free(struct cgroup_subsys_state *css)
232 {
233         struct dev_cgroup *dev_cgroup = css_to_devcgroup(css);
234
235         __dev_exception_clean(dev_cgroup);
236         kfree(dev_cgroup);
237 }
238
239 #define DEVCG_ALLOW 1
240 #define DEVCG_DENY 2
241 #define DEVCG_LIST 3
242
243 #define MAJMINLEN 13
244 #define ACCLEN 4
245
246 static void set_access(char *acc, short access)
247 {
248         int idx = 0;
249         memset(acc, 0, ACCLEN);
250         if (access & ACC_READ)
251                 acc[idx++] = 'r';
252         if (access & ACC_WRITE)
253                 acc[idx++] = 'w';
254         if (access & ACC_MKNOD)
255                 acc[idx++] = 'm';
256 }
257
258 static char type_to_char(short type)
259 {
260         if (type == DEV_ALL)
261                 return 'a';
262         if (type == DEV_CHAR)
263                 return 'c';
264         if (type == DEV_BLOCK)
265                 return 'b';
266         return 'X';
267 }
268
269 static void set_majmin(char *str, unsigned m)
270 {
271         if (m == ~0)
272                 strcpy(str, "*");
273         else
274                 sprintf(str, "%u", m);
275 }
276
277 static int devcgroup_seq_read(struct cgroup_subsys_state *css,
278                               struct cftype *cft, struct seq_file *m)
279 {
280         struct dev_cgroup *devcgroup = css_to_devcgroup(css);
281         struct dev_exception_item *ex;
282         char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN];
283
284         rcu_read_lock();
285         /*
286          * To preserve the compatibility:
287          * - Only show the "all devices" when the default policy is to allow
288          * - List the exceptions in case the default policy is to deny
289          * This way, the file remains as a "whitelist of devices"
290          */
291         if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) {
292                 set_access(acc, ACC_MASK);
293                 set_majmin(maj, ~0);
294                 set_majmin(min, ~0);
295                 seq_printf(m, "%c %s:%s %s\n", type_to_char(DEV_ALL),
296                            maj, min, acc);
297         } else {
298                 list_for_each_entry_rcu(ex, &devcgroup->exceptions, list) {
299                         set_access(acc, ex->access);
300                         set_majmin(maj, ex->major);
301                         set_majmin(min, ex->minor);
302                         seq_printf(m, "%c %s:%s %s\n", type_to_char(ex->type),
303                                    maj, min, acc);
304                 }
305         }
306         rcu_read_unlock();
307
308         return 0;
309 }
310
311 /**
312  * may_access - verifies if a new exception is part of what is allowed
313  *              by a dev cgroup based on the default policy +
314  *              exceptions. This is used to make sure a child cgroup
315  *              won't have more privileges than its parent or to
316  *              verify if a certain access is allowed.
317  * @dev_cgroup: dev cgroup to be tested against
318  * @refex: new exception
319  * @behavior: behavior of the exception
320  */
321 static bool may_access(struct dev_cgroup *dev_cgroup,
322                        struct dev_exception_item *refex,
323                        enum devcg_behavior behavior)
324 {
325         struct dev_exception_item *ex;
326         bool match = false;
327
328         rcu_lockdep_assert(rcu_read_lock_held() ||
329                            lockdep_is_held(&devcgroup_mutex),
330                            "device_cgroup::may_access() called without proper synchronization");
331
332         list_for_each_entry_rcu(ex, &dev_cgroup->exceptions, list) {
333                 if ((refex->type & DEV_BLOCK) && !(ex->type & DEV_BLOCK))
334                         continue;
335                 if ((refex->type & DEV_CHAR) && !(ex->type & DEV_CHAR))
336                         continue;
337                 if (ex->major != ~0 && ex->major != refex->major)
338                         continue;
339                 if (ex->minor != ~0 && ex->minor != refex->minor)
340                         continue;
341                 if (refex->access & (~ex->access))
342                         continue;
343                 match = true;
344                 break;
345         }
346
347         if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) {
348                 if (behavior == DEVCG_DEFAULT_ALLOW) {
349                         /* the exception will deny access to certain devices */
350                         return true;
351                 } else {
352                         /* the exception will allow access to certain devices */
353                         if (match)
354                                 /*
355                                  * a new exception allowing access shouldn't
356                                  * match an parent's exception
357                                  */
358                                 return false;
359                         return true;
360                 }
361         } else {
362                 /* only behavior == DEVCG_DEFAULT_DENY allowed here */
363                 if (match)
364                         /* parent has an exception that matches the proposed */
365                         return true;
366                 else
367                         return false;
368         }
369         return false;
370 }
371
372 /*
373  * parent_has_perm:
374  * when adding a new allow rule to a device exception list, the rule
375  * must be allowed in the parent device
376  */
377 static int parent_has_perm(struct dev_cgroup *childcg,
378                                   struct dev_exception_item *ex)
379 {
380         struct dev_cgroup *parent = css_to_devcgroup(css_parent(&childcg->css));
381
382         if (!parent)
383                 return 1;
384         return may_access(parent, ex, childcg->behavior);
385 }
386
387 /**
388  * may_allow_all - checks if it's possible to change the behavior to
389  *                 allow based on parent's rules.
390  * @parent: device cgroup's parent
391  * returns: != 0 in case it's allowed, 0 otherwise
392  */
393 static inline int may_allow_all(struct dev_cgroup *parent)
394 {
395         if (!parent)
396                 return 1;
397         return parent->behavior == DEVCG_DEFAULT_ALLOW;
398 }
399
400 /**
401  * revalidate_active_exceptions - walks through the active exception list and
402  *                                revalidates the exceptions based on parent's
403  *                                behavior and exceptions. The exceptions that
404  *                                are no longer valid will be removed.
405  *                                Called with devcgroup_mutex held.
406  * @devcg: cgroup which exceptions will be checked
407  *
408  * This is one of the three key functions for hierarchy implementation.
409  * This function is responsible for re-evaluating all the cgroup's active
410  * exceptions due to a parent's exception change.
411  * Refer to Documentation/cgroups/devices.txt for more details.
412  */
413 static void revalidate_active_exceptions(struct dev_cgroup *devcg)
414 {
415         struct dev_exception_item *ex;
416         struct list_head *this, *tmp;
417
418         list_for_each_safe(this, tmp, &devcg->exceptions) {
419                 ex = container_of(this, struct dev_exception_item, list);
420                 if (!parent_has_perm(devcg, ex))
421                         dev_exception_rm(devcg, ex);
422         }
423 }
424
425 /**
426  * propagate_exception - propagates a new exception to the children
427  * @devcg_root: device cgroup that added a new exception
428  * @ex: new exception to be propagated
429  *
430  * returns: 0 in case of success, != 0 in case of error
431  */
432 static int propagate_exception(struct dev_cgroup *devcg_root,
433                                struct dev_exception_item *ex)
434 {
435         struct cgroup_subsys_state *pos;
436         int rc = 0;
437
438         rcu_read_lock();
439
440         css_for_each_descendant_pre(pos, &devcg_root->css) {
441                 struct dev_cgroup *devcg = css_to_devcgroup(pos);
442
443                 /*
444                  * Because devcgroup_mutex is held, no devcg will become
445                  * online or offline during the tree walk (see on/offline
446                  * methods), and online ones are safe to access outside RCU
447                  * read lock without bumping refcnt.
448                  */
449                 if (pos == &devcg_root->css || !is_devcg_online(devcg))
450                         continue;
451
452                 rcu_read_unlock();
453
454                 /*
455                  * in case both root's behavior and devcg is allow, a new
456                  * restriction means adding to the exception list
457                  */
458                 if (devcg_root->behavior == DEVCG_DEFAULT_ALLOW &&
459                     devcg->behavior == DEVCG_DEFAULT_ALLOW) {
460                         rc = dev_exception_add(devcg, ex);
461                         if (rc)
462                                 break;
463                 } else {
464                         /*
465                          * in the other possible cases:
466                          * root's behavior: allow, devcg's: deny
467                          * root's behavior: deny, devcg's: deny
468                          * the exception will be removed
469                          */
470                         dev_exception_rm(devcg, ex);
471                 }
472                 revalidate_active_exceptions(devcg);
473
474                 rcu_read_lock();
475         }
476
477         rcu_read_unlock();
478         return rc;
479 }
480
481 static inline bool has_children(struct dev_cgroup *devcgroup)
482 {
483         struct cgroup *cgrp = devcgroup->css.cgroup;
484
485         return !list_empty(&cgrp->children);
486 }
487
488 /*
489  * Modify the exception list using allow/deny rules.
490  * CAP_SYS_ADMIN is needed for this.  It's at least separate from CAP_MKNOD
491  * so we can give a container CAP_MKNOD to let it create devices but not
492  * modify the exception list.
493  * It seems likely we'll want to add a CAP_CONTAINER capability to allow
494  * us to also grant CAP_SYS_ADMIN to containers without giving away the
495  * device exception list controls, but for now we'll stick with CAP_SYS_ADMIN
496  *
497  * Taking rules away is always allowed (given CAP_SYS_ADMIN).  Granting
498  * new access is only allowed if you're in the top-level cgroup, or your
499  * parent cgroup has the access you're asking for.
500  */
501 static int devcgroup_update_access(struct dev_cgroup *devcgroup,
502                                    int filetype, const char *buffer)
503 {
504         const char *b;
505         char temp[12];          /* 11 + 1 characters needed for a u32 */
506         int count, rc = 0;
507         struct dev_exception_item ex;
508         struct dev_cgroup *parent = css_to_devcgroup(css_parent(&devcgroup->css));
509
510         if (!capable(CAP_SYS_ADMIN))
511                 return -EPERM;
512
513         memset(&ex, 0, sizeof(ex));
514         b = buffer;
515
516         switch (*b) {
517         case 'a':
518                 switch (filetype) {
519                 case DEVCG_ALLOW:
520                         if (has_children(devcgroup))
521                                 return -EINVAL;
522
523                         if (!may_allow_all(parent))
524                                 return -EPERM;
525                         dev_exception_clean(devcgroup);
526                         devcgroup->behavior = DEVCG_DEFAULT_ALLOW;
527                         if (!parent)
528                                 break;
529
530                         rc = dev_exceptions_copy(&devcgroup->exceptions,
531                                                  &parent->exceptions);
532                         if (rc)
533                                 return rc;
534                         break;
535                 case DEVCG_DENY:
536                         if (has_children(devcgroup))
537                                 return -EINVAL;
538
539                         dev_exception_clean(devcgroup);
540                         devcgroup->behavior = DEVCG_DEFAULT_DENY;
541                         break;
542                 default:
543                         return -EINVAL;
544                 }
545                 return 0;
546         case 'b':
547                 ex.type = DEV_BLOCK;
548                 break;
549         case 'c':
550                 ex.type = DEV_CHAR;
551                 break;
552         default:
553                 return -EINVAL;
554         }
555         b++;
556         if (!isspace(*b))
557                 return -EINVAL;
558         b++;
559         if (*b == '*') {
560                 ex.major = ~0;
561                 b++;
562         } else if (isdigit(*b)) {
563                 memset(temp, 0, sizeof(temp));
564                 for (count = 0; count < sizeof(temp) - 1; count++) {
565                         temp[count] = *b;
566                         b++;
567                         if (!isdigit(*b))
568                                 break;
569                 }
570                 rc = kstrtou32(temp, 10, &ex.major);
571                 if (rc)
572                         return -EINVAL;
573         } else {
574                 return -EINVAL;
575         }
576         if (*b != ':')
577                 return -EINVAL;
578         b++;
579
580         /* read minor */
581         if (*b == '*') {
582                 ex.minor = ~0;
583                 b++;
584         } else if (isdigit(*b)) {
585                 memset(temp, 0, sizeof(temp));
586                 for (count = 0; count < sizeof(temp) - 1; count++) {
587                         temp[count] = *b;
588                         b++;
589                         if (!isdigit(*b))
590                                 break;
591                 }
592                 rc = kstrtou32(temp, 10, &ex.minor);
593                 if (rc)
594                         return -EINVAL;
595         } else {
596                 return -EINVAL;
597         }
598         if (!isspace(*b))
599                 return -EINVAL;
600         for (b++, count = 0; count < 3; count++, b++) {
601                 switch (*b) {
602                 case 'r':
603                         ex.access |= ACC_READ;
604                         break;
605                 case 'w':
606                         ex.access |= ACC_WRITE;
607                         break;
608                 case 'm':
609                         ex.access |= ACC_MKNOD;
610                         break;
611                 case '\n':
612                 case '\0':
613                         count = 3;
614                         break;
615                 default:
616                         return -EINVAL;
617                 }
618         }
619
620         switch (filetype) {
621         case DEVCG_ALLOW:
622                 if (!parent_has_perm(devcgroup, &ex))
623                         return -EPERM;
624                 /*
625                  * If the default policy is to allow by default, try to remove
626                  * an matching exception instead. And be silent about it: we
627                  * don't want to break compatibility
628                  */
629                 if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) {
630                         dev_exception_rm(devcgroup, &ex);
631                         return 0;
632                 }
633                 rc = dev_exception_add(devcgroup, &ex);
634                 break;
635         case DEVCG_DENY:
636                 /*
637                  * If the default policy is to deny by default, try to remove
638                  * an matching exception instead. And be silent about it: we
639                  * don't want to break compatibility
640                  */
641                 if (devcgroup->behavior == DEVCG_DEFAULT_DENY)
642                         dev_exception_rm(devcgroup, &ex);
643                 else
644                         rc = dev_exception_add(devcgroup, &ex);
645
646                 if (rc)
647                         break;
648                 /* we only propagate new restrictions */
649                 rc = propagate_exception(devcgroup, &ex);
650                 break;
651         default:
652                 rc = -EINVAL;
653         }
654         return rc;
655 }
656
657 static int devcgroup_access_write(struct cgroup_subsys_state *css,
658                                   struct cftype *cft, const char *buffer)
659 {
660         int retval;
661
662         mutex_lock(&devcgroup_mutex);
663         retval = devcgroup_update_access(css_to_devcgroup(css),
664                                          cft->private, buffer);
665         mutex_unlock(&devcgroup_mutex);
666         return retval;
667 }
668
669 static struct cftype dev_cgroup_files[] = {
670         {
671                 .name = "allow",
672                 .write_string  = devcgroup_access_write,
673                 .private = DEVCG_ALLOW,
674         },
675         {
676                 .name = "deny",
677                 .write_string = devcgroup_access_write,
678                 .private = DEVCG_DENY,
679         },
680         {
681                 .name = "list",
682                 .read_seq_string = devcgroup_seq_read,
683                 .private = DEVCG_LIST,
684         },
685         { }     /* terminate */
686 };
687
688 struct cgroup_subsys devices_subsys = {
689         .name = "devices",
690         .css_alloc = devcgroup_css_alloc,
691         .css_free = devcgroup_css_free,
692         .css_online = devcgroup_online,
693         .css_offline = devcgroup_offline,
694         .subsys_id = devices_subsys_id,
695         .base_cftypes = dev_cgroup_files,
696 };
697
698 /**
699  * __devcgroup_check_permission - checks if an inode operation is permitted
700  * @dev_cgroup: the dev cgroup to be tested against
701  * @type: device type
702  * @major: device major number
703  * @minor: device minor number
704  * @access: combination of ACC_WRITE, ACC_READ and ACC_MKNOD
705  *
706  * returns 0 on success, -EPERM case the operation is not permitted
707  */
708 static int __devcgroup_check_permission(short type, u32 major, u32 minor,
709                                         short access)
710 {
711         struct dev_cgroup *dev_cgroup;
712         struct dev_exception_item ex;
713         int rc;
714
715         memset(&ex, 0, sizeof(ex));
716         ex.type = type;
717         ex.major = major;
718         ex.minor = minor;
719         ex.access = access;
720
721         rcu_read_lock();
722         dev_cgroup = task_devcgroup(current);
723         rc = may_access(dev_cgroup, &ex, dev_cgroup->behavior);
724         rcu_read_unlock();
725
726         if (!rc)
727                 return -EPERM;
728
729         return 0;
730 }
731
732 int __devcgroup_inode_permission(struct inode *inode, int mask)
733 {
734         short type, access = 0;
735
736         if (S_ISBLK(inode->i_mode))
737                 type = DEV_BLOCK;
738         if (S_ISCHR(inode->i_mode))
739                 type = DEV_CHAR;
740         if (mask & MAY_WRITE)
741                 access |= ACC_WRITE;
742         if (mask & MAY_READ)
743                 access |= ACC_READ;
744
745         return __devcgroup_check_permission(type, imajor(inode), iminor(inode),
746                         access);
747 }
748
749 int devcgroup_inode_mknod(int mode, dev_t dev)
750 {
751         short type;
752
753         if (!S_ISBLK(mode) && !S_ISCHR(mode))
754                 return 0;
755
756         if (S_ISBLK(mode))
757                 type = DEV_BLOCK;
758         else
759                 type = DEV_CHAR;
760
761         return __devcgroup_check_permission(type, MAJOR(dev), MINOR(dev),
762                         ACC_MKNOD);
763
764 }