diff options
author | Eric Paris <eparis@redhat.com> | 2008-03-05 10:31:54 -0500 |
---|---|---|
committer | James Morris <jmorris@namei.org> | 2008-03-05 16:40:53 -0500 |
commit | e0007529893c1c064be90bd21422ca0da4a0198e (patch) | |
tree | c2334ba940e682183a18d18972cf95bd3a3da46a /security/selinux/hooks.c | |
parent | 29e8c3c304b62f31b799565c9ee85d42bd163f80 (diff) |
LSM/SELinux: Interfaces to allow FS to control mount options
Introduce new LSM interfaces to allow an FS to deal with their own mount
options. This includes a new string parsing function exported from the
LSM that an FS can use to get a security data blob and a new security
data blob. This is particularly useful for an FS which uses binary
mount data, like NFS, which does not pass strings into the vfs to be
handled by the loaded LSM. Also fix a BUG() in both SELinux and SMACK
when dealing with binary mount data. If the binary mount data is less
than one page the copy_page() in security_sb_copy_data() can cause an
illegal page fault and boom. Remove all NFSisms from the SELinux code
since they were broken by past NFS changes.
Signed-off-by: Eric Paris <eparis@redhat.com>
Acked-by: Stephen Smalley <sds@tycho.nsa.gov>
Acked-by: Casey Schaufler <casey@schaufler-ca.com>
Signed-off-by: James Morris <jmorris@namei.org>
Diffstat (limited to 'security/selinux/hooks.c')
-rw-r--r-- | security/selinux/hooks.c | 175 |
1 files changed, 94 insertions, 81 deletions
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 75c2e99bfb81..4bf4807f2d44 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c | |||
@@ -443,8 +443,7 @@ out: | |||
443 | * mount options, or whatever. | 443 | * mount options, or whatever. |
444 | */ | 444 | */ |
445 | static int selinux_get_mnt_opts(const struct super_block *sb, | 445 | static int selinux_get_mnt_opts(const struct super_block *sb, |
446 | char ***mount_options, int **mnt_opts_flags, | 446 | struct security_mnt_opts *opts) |
447 | int *num_opts) | ||
448 | { | 447 | { |
449 | int rc = 0, i; | 448 | int rc = 0, i; |
450 | struct superblock_security_struct *sbsec = sb->s_security; | 449 | struct superblock_security_struct *sbsec = sb->s_security; |
@@ -452,9 +451,7 @@ static int selinux_get_mnt_opts(const struct super_block *sb, | |||
452 | u32 len; | 451 | u32 len; |
453 | char tmp; | 452 | char tmp; |
454 | 453 | ||
455 | *num_opts = 0; | 454 | security_init_mnt_opts(opts); |
456 | *mount_options = NULL; | ||
457 | *mnt_opts_flags = NULL; | ||
458 | 455 | ||
459 | if (!sbsec->initialized) | 456 | if (!sbsec->initialized) |
460 | return -EINVAL; | 457 | return -EINVAL; |
@@ -470,18 +467,18 @@ static int selinux_get_mnt_opts(const struct super_block *sb, | |||
470 | /* count the number of mount options for this sb */ | 467 | /* count the number of mount options for this sb */ |
471 | for (i = 0; i < 8; i++) { | 468 | for (i = 0; i < 8; i++) { |
472 | if (tmp & 0x01) | 469 | if (tmp & 0x01) |
473 | (*num_opts)++; | 470 | opts->num_mnt_opts++; |
474 | tmp >>= 1; | 471 | tmp >>= 1; |
475 | } | 472 | } |
476 | 473 | ||
477 | *mount_options = kcalloc(*num_opts, sizeof(char *), GFP_ATOMIC); | 474 | opts->mnt_opts = kcalloc(opts->num_mnt_opts, sizeof(char *), GFP_ATOMIC); |
478 | if (!*mount_options) { | 475 | if (!opts->mnt_opts) { |
479 | rc = -ENOMEM; | 476 | rc = -ENOMEM; |
480 | goto out_free; | 477 | goto out_free; |
481 | } | 478 | } |
482 | 479 | ||
483 | *mnt_opts_flags = kcalloc(*num_opts, sizeof(int), GFP_ATOMIC); | 480 | opts->mnt_opts_flags = kcalloc(opts->num_mnt_opts, sizeof(int), GFP_ATOMIC); |
484 | if (!*mnt_opts_flags) { | 481 | if (!opts->mnt_opts_flags) { |
485 | rc = -ENOMEM; | 482 | rc = -ENOMEM; |
486 | goto out_free; | 483 | goto out_free; |
487 | } | 484 | } |
@@ -491,22 +488,22 @@ static int selinux_get_mnt_opts(const struct super_block *sb, | |||
491 | rc = security_sid_to_context(sbsec->sid, &context, &len); | 488 | rc = security_sid_to_context(sbsec->sid, &context, &len); |
492 | if (rc) | 489 | if (rc) |
493 | goto out_free; | 490 | goto out_free; |
494 | (*mount_options)[i] = context; | 491 | opts->mnt_opts[i] = context; |
495 | (*mnt_opts_flags)[i++] = FSCONTEXT_MNT; | 492 | opts->mnt_opts_flags[i++] = FSCONTEXT_MNT; |
496 | } | 493 | } |
497 | if (sbsec->flags & CONTEXT_MNT) { | 494 | if (sbsec->flags & CONTEXT_MNT) { |
498 | rc = security_sid_to_context(sbsec->mntpoint_sid, &context, &len); | 495 | rc = security_sid_to_context(sbsec->mntpoint_sid, &context, &len); |
499 | if (rc) | 496 | if (rc) |
500 | goto out_free; | 497 | goto out_free; |
501 | (*mount_options)[i] = context; | 498 | opts->mnt_opts[i] = context; |
502 | (*mnt_opts_flags)[i++] = CONTEXT_MNT; | 499 | opts->mnt_opts_flags[i++] = CONTEXT_MNT; |
503 | } | 500 | } |
504 | if (sbsec->flags & DEFCONTEXT_MNT) { | 501 | if (sbsec->flags & DEFCONTEXT_MNT) { |
505 | rc = security_sid_to_context(sbsec->def_sid, &context, &len); | 502 | rc = security_sid_to_context(sbsec->def_sid, &context, &len); |
506 | if (rc) | 503 | if (rc) |
507 | goto out_free; | 504 | goto out_free; |
508 | (*mount_options)[i] = context; | 505 | opts->mnt_opts[i] = context; |
509 | (*mnt_opts_flags)[i++] = DEFCONTEXT_MNT; | 506 | opts->mnt_opts_flags[i++] = DEFCONTEXT_MNT; |
510 | } | 507 | } |
511 | if (sbsec->flags & ROOTCONTEXT_MNT) { | 508 | if (sbsec->flags & ROOTCONTEXT_MNT) { |
512 | struct inode *root = sbsec->sb->s_root->d_inode; | 509 | struct inode *root = sbsec->sb->s_root->d_inode; |
@@ -515,24 +512,16 @@ static int selinux_get_mnt_opts(const struct super_block *sb, | |||
515 | rc = security_sid_to_context(isec->sid, &context, &len); | 512 | rc = security_sid_to_context(isec->sid, &context, &len); |
516 | if (rc) | 513 | if (rc) |
517 | goto out_free; | 514 | goto out_free; |
518 | (*mount_options)[i] = context; | 515 | opts->mnt_opts[i] = context; |
519 | (*mnt_opts_flags)[i++] = ROOTCONTEXT_MNT; | 516 | opts->mnt_opts_flags[i++] = ROOTCONTEXT_MNT; |
520 | } | 517 | } |
521 | 518 | ||
522 | BUG_ON(i != *num_opts); | 519 | BUG_ON(i != opts->num_mnt_opts); |
523 | 520 | ||
524 | return 0; | 521 | return 0; |
525 | 522 | ||
526 | out_free: | 523 | out_free: |
527 | /* don't leak context string if security_sid_to_context had an error */ | 524 | security_free_mnt_opts(opts); |
528 | if (*mount_options && i) | ||
529 | for (; i > 0; i--) | ||
530 | kfree((*mount_options)[i-1]); | ||
531 | kfree(*mount_options); | ||
532 | *mount_options = NULL; | ||
533 | kfree(*mnt_opts_flags); | ||
534 | *mnt_opts_flags = NULL; | ||
535 | *num_opts = 0; | ||
536 | return rc; | 525 | return rc; |
537 | } | 526 | } |
538 | 527 | ||
@@ -553,12 +542,13 @@ static int bad_option(struct superblock_security_struct *sbsec, char flag, | |||
553 | return 1; | 542 | return 1; |
554 | return 0; | 543 | return 0; |
555 | } | 544 | } |
545 | |||
556 | /* | 546 | /* |
557 | * Allow filesystems with binary mount data to explicitly set mount point | 547 | * Allow filesystems with binary mount data to explicitly set mount point |
558 | * labeling information. | 548 | * labeling information. |
559 | */ | 549 | */ |
560 | static int selinux_set_mnt_opts(struct super_block *sb, char **mount_options, | 550 | static int selinux_set_mnt_opts(struct super_block *sb, |
561 | int *flags, int num_opts) | 551 | struct security_mnt_opts *opts) |
562 | { | 552 | { |
563 | int rc = 0, i; | 553 | int rc = 0, i; |
564 | struct task_security_struct *tsec = current->security; | 554 | struct task_security_struct *tsec = current->security; |
@@ -568,6 +558,9 @@ static int selinux_set_mnt_opts(struct super_block *sb, char **mount_options, | |||
568 | struct inode_security_struct *root_isec = inode->i_security; | 558 | struct inode_security_struct *root_isec = inode->i_security; |
569 | u32 fscontext_sid = 0, context_sid = 0, rootcontext_sid = 0; | 559 | u32 fscontext_sid = 0, context_sid = 0, rootcontext_sid = 0; |
570 | u32 defcontext_sid = 0; | 560 | u32 defcontext_sid = 0; |
561 | char **mount_options = opts->mnt_opts; | ||
562 | int *flags = opts->mnt_opts_flags; | ||
563 | int num_opts = opts->num_mnt_opts; | ||
571 | 564 | ||
572 | mutex_lock(&sbsec->lock); | 565 | mutex_lock(&sbsec->lock); |
573 | 566 | ||
@@ -589,6 +582,21 @@ static int selinux_set_mnt_opts(struct super_block *sb, char **mount_options, | |||
589 | } | 582 | } |
590 | 583 | ||
591 | /* | 584 | /* |
585 | * Binary mount data FS will come through this function twice. Once | ||
586 | * from an explicit call and once from the generic calls from the vfs. | ||
587 | * Since the generic VFS calls will not contain any security mount data | ||
588 | * we need to skip the double mount verification. | ||
589 | * | ||
590 | * This does open a hole in which we will not notice if the first | ||
591 | * mount using this sb set explict options and a second mount using | ||
592 | * this sb does not set any security options. (The first options | ||
593 | * will be used for both mounts) | ||
594 | */ | ||
595 | if (sbsec->initialized && (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) | ||
596 | && (num_opts == 0)) | ||
597 | goto out; | ||
598 | |||
599 | /* | ||
592 | * parse the mount options, check if they are valid sids. | 600 | * parse the mount options, check if they are valid sids. |
593 | * also check if someone is trying to mount the same sb more | 601 | * also check if someone is trying to mount the same sb more |
594 | * than once with different security options. | 602 | * than once with different security options. |
@@ -792,43 +800,14 @@ static void selinux_sb_clone_mnt_opts(const struct super_block *oldsb, | |||
792 | mutex_unlock(&newsbsec->lock); | 800 | mutex_unlock(&newsbsec->lock); |
793 | } | 801 | } |
794 | 802 | ||
795 | /* | 803 | int selinux_parse_opts_str(char *options, struct security_mnt_opts *opts) |
796 | * string mount options parsing and call set the sbsec | ||
797 | */ | ||
798 | static int superblock_doinit(struct super_block *sb, void *data) | ||
799 | { | 804 | { |
805 | char *p; | ||
800 | char *context = NULL, *defcontext = NULL; | 806 | char *context = NULL, *defcontext = NULL; |
801 | char *fscontext = NULL, *rootcontext = NULL; | 807 | char *fscontext = NULL, *rootcontext = NULL; |
802 | int rc = 0; | 808 | int rc, num_mnt_opts = 0; |
803 | char *p, *options = data; | ||
804 | /* selinux only know about a fixed number of mount options */ | ||
805 | char *mnt_opts[NUM_SEL_MNT_OPTS]; | ||
806 | int mnt_opts_flags[NUM_SEL_MNT_OPTS], num_mnt_opts = 0; | ||
807 | |||
808 | if (!data) | ||
809 | goto out; | ||
810 | 809 | ||
811 | /* with the nfs patch this will become a goto out; */ | 810 | opts->num_mnt_opts = 0; |
812 | if (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) { | ||
813 | const char *name = sb->s_type->name; | ||
814 | /* NFS we understand. */ | ||
815 | if (!strcmp(name, "nfs")) { | ||
816 | struct nfs_mount_data *d = data; | ||
817 | |||
818 | if (d->version != NFS_MOUNT_VERSION) | ||
819 | goto out; | ||
820 | |||
821 | if (d->context[0]) { | ||
822 | context = kstrdup(d->context, GFP_KERNEL); | ||
823 | if (!context) { | ||
824 | rc = -ENOMEM; | ||
825 | goto out; | ||
826 | } | ||
827 | } | ||
828 | goto build_flags; | ||
829 | } else | ||
830 | goto out; | ||
831 | } | ||
832 | 811 | ||
833 | /* Standard string-based options. */ | 812 | /* Standard string-based options. */ |
834 | while ((p = strsep(&options, "|")) != NULL) { | 813 | while ((p = strsep(&options, "|")) != NULL) { |
@@ -901,26 +880,37 @@ static int superblock_doinit(struct super_block *sb, void *data) | |||
901 | } | 880 | } |
902 | } | 881 | } |
903 | 882 | ||
904 | build_flags: | 883 | rc = -ENOMEM; |
884 | opts->mnt_opts = kcalloc(NUM_SEL_MNT_OPTS, sizeof(char *), GFP_ATOMIC); | ||
885 | if (!opts->mnt_opts) | ||
886 | goto out_err; | ||
887 | |||
888 | opts->mnt_opts_flags = kcalloc(NUM_SEL_MNT_OPTS, sizeof(int), GFP_ATOMIC); | ||
889 | if (!opts->mnt_opts_flags) { | ||
890 | kfree(opts->mnt_opts); | ||
891 | goto out_err; | ||
892 | } | ||
893 | |||
905 | if (fscontext) { | 894 | if (fscontext) { |
906 | mnt_opts[num_mnt_opts] = fscontext; | 895 | opts->mnt_opts[num_mnt_opts] = fscontext; |
907 | mnt_opts_flags[num_mnt_opts++] = FSCONTEXT_MNT; | 896 | opts->mnt_opts_flags[num_mnt_opts++] = FSCONTEXT_MNT; |
908 | } | 897 | } |
909 | if (context) { | 898 | if (context) { |
910 | mnt_opts[num_mnt_opts] = context; | 899 | opts->mnt_opts[num_mnt_opts] = context; |
911 | mnt_opts_flags[num_mnt_opts++] = CONTEXT_MNT; | 900 | opts->mnt_opts_flags[num_mnt_opts++] = CONTEXT_MNT; |
912 | } | 901 | } |
913 | if (rootcontext) { | 902 | if (rootcontext) { |
914 | mnt_opts[num_mnt_opts] = rootcontext; | 903 | opts->mnt_opts[num_mnt_opts] = rootcontext; |
915 | mnt_opts_flags[num_mnt_opts++] = ROOTCONTEXT_MNT; | 904 | opts->mnt_opts_flags[num_mnt_opts++] = ROOTCONTEXT_MNT; |
916 | } | 905 | } |
917 | if (defcontext) { | 906 | if (defcontext) { |
918 | mnt_opts[num_mnt_opts] = defcontext; | 907 | opts->mnt_opts[num_mnt_opts] = defcontext; |
919 | mnt_opts_flags[num_mnt_opts++] = DEFCONTEXT_MNT; | 908 | opts->mnt_opts_flags[num_mnt_opts++] = DEFCONTEXT_MNT; |
920 | } | 909 | } |
921 | 910 | ||
922 | out: | 911 | opts->num_mnt_opts = num_mnt_opts; |
923 | rc = selinux_set_mnt_opts(sb, mnt_opts, mnt_opts_flags, num_mnt_opts); | 912 | return 0; |
913 | |||
924 | out_err: | 914 | out_err: |
925 | kfree(context); | 915 | kfree(context); |
926 | kfree(defcontext); | 916 | kfree(defcontext); |
@@ -928,6 +918,33 @@ out_err: | |||
928 | kfree(rootcontext); | 918 | kfree(rootcontext); |
929 | return rc; | 919 | return rc; |
930 | } | 920 | } |
921 | /* | ||
922 | * string mount options parsing and call set the sbsec | ||
923 | */ | ||
924 | static int superblock_doinit(struct super_block *sb, void *data) | ||
925 | { | ||
926 | int rc = 0; | ||
927 | char *options = data; | ||
928 | struct security_mnt_opts opts; | ||
929 | |||
930 | security_init_mnt_opts(&opts); | ||
931 | |||
932 | if (!data) | ||
933 | goto out; | ||
934 | |||
935 | BUG_ON(sb->s_type->fs_flags & FS_BINARY_MOUNTDATA); | ||
936 | |||
937 | rc = selinux_parse_opts_str(options, &opts); | ||
938 | if (rc) | ||
939 | goto out_err; | ||
940 | |||
941 | out: | ||
942 | rc = selinux_set_mnt_opts(sb, &opts); | ||
943 | |||
944 | out_err: | ||
945 | security_free_mnt_opts(&opts); | ||
946 | return rc; | ||
947 | } | ||
931 | 948 | ||
932 | static inline u16 inode_mode_to_security_class(umode_t mode) | 949 | static inline u16 inode_mode_to_security_class(umode_t mode) |
933 | { | 950 | { |
@@ -2253,7 +2270,7 @@ static inline void take_selinux_option(char **to, char *from, int *first, | |||
2253 | } | 2270 | } |
2254 | } | 2271 | } |
2255 | 2272 | ||
2256 | static int selinux_sb_copy_data(struct file_system_type *type, void *orig, void *copy) | 2273 | static int selinux_sb_copy_data(char *orig, char *copy) |
2257 | { | 2274 | { |
2258 | int fnosec, fsec, rc = 0; | 2275 | int fnosec, fsec, rc = 0; |
2259 | char *in_save, *in_curr, *in_end; | 2276 | char *in_save, *in_curr, *in_end; |
@@ -2263,12 +2280,6 @@ static int selinux_sb_copy_data(struct file_system_type *type, void *orig, void | |||
2263 | in_curr = orig; | 2280 | in_curr = orig; |
2264 | sec_curr = copy; | 2281 | sec_curr = copy; |
2265 | 2282 | ||
2266 | /* Binary mount data: just copy */ | ||
2267 | if (type->fs_flags & FS_BINARY_MOUNTDATA) { | ||
2268 | copy_page(sec_curr, in_curr); | ||
2269 | goto out; | ||
2270 | } | ||
2271 | |||
2272 | nosec = (char *)get_zeroed_page(GFP_KERNEL); | 2283 | nosec = (char *)get_zeroed_page(GFP_KERNEL); |
2273 | if (!nosec) { | 2284 | if (!nosec) { |
2274 | rc = -ENOMEM; | 2285 | rc = -ENOMEM; |
@@ -5251,6 +5262,8 @@ static struct security_operations selinux_ops = { | |||
5251 | .sb_get_mnt_opts = selinux_get_mnt_opts, | 5262 | .sb_get_mnt_opts = selinux_get_mnt_opts, |
5252 | .sb_set_mnt_opts = selinux_set_mnt_opts, | 5263 | .sb_set_mnt_opts = selinux_set_mnt_opts, |
5253 | .sb_clone_mnt_opts = selinux_sb_clone_mnt_opts, | 5264 | .sb_clone_mnt_opts = selinux_sb_clone_mnt_opts, |
5265 | .sb_parse_opts_str = selinux_parse_opts_str, | ||
5266 | |||
5254 | 5267 | ||
5255 | .inode_alloc_security = selinux_inode_alloc_security, | 5268 | .inode_alloc_security = selinux_inode_alloc_security, |
5256 | .inode_free_security = selinux_inode_free_security, | 5269 | .inode_free_security = selinux_inode_free_security, |