Skip to content

Commit 674e2b2

Browse files
goongascschaufler
authored andcommitted
smack: fix bug: setting task label silently ignores input garbage
This command: # echo foo/bar >/proc/$$/attr/smack/current gives the task a label 'foo' w/o indication that label does not match input. Setting the label with lsm_set_self_attr() syscall behaves identically. This occures because: 1) smk_parse_smack() is used to convert input to a label 2) smk_parse_smack() takes only that part from the beginning of the input that looks like a label. 3) `/' is prohibited in labels, so only "foo" is taken. (2) is by design, because smk_parse_smack() is used for parsing strings which are more than just a label. Silent failure is not a good thing, and there are two indicators that this was not done intentionally: (size >= SMK_LONGLABEL) ~> invalid clause at the beginning of the do_setattr() and the "Returns the length of the smack label" claim in the do_setattr() description. So I fixed this by adding one tiny check: the taken label length == input length. Since input length is now strictly controlled, I changed the two ways of setting label smack_setselfattr(): lsm_set_self_attr() syscall smack_setprocattr(): > /proc/.../current to accommodate the divergence in what they understand by "input length": smack_setselfattr counts mandatory \0 into input length, smack_setprocattr does not. smack_setprocattr allows various trailers after label Related changes: * fixed description for smk_parse_smack * allow unprivileged tasks validate label syntax. * extract smk_parse_label_len() from smk_parse_smack() so parsing may be done w/o string allocation. * extract smk_import_valid_label() from smk_import_entry() to avoid repeated parsing. * smk_parse_smack(): scan null-terminated strings for no more than SMK_LONGLABEL(256) characters * smack_setselfattr(): require struct lsm_ctx . flags == 0 to reserve them for future. Fixes: e114e47 ("Smack: Simplified Mandatory Access Control Kernel") Signed-off-by: Konstantin Andreev <andreev@swemel.ru> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
1 parent c147e13 commit 674e2b2

4 files changed

Lines changed: 156 additions & 66 deletions

File tree

Documentation/admin-guide/LSM/Smack.rst

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -601,10 +601,15 @@ specification.
601601
Task Attribute
602602
~~~~~~~~~~~~~~
603603

604-
The Smack label of a process can be read from /proc/<pid>/attr/current. A
605-
process can read its own Smack label from /proc/self/attr/current. A
604+
The Smack label of a process can be read from ``/proc/<pid>/attr/current``. A
605+
process can read its own Smack label from ``/proc/self/attr/current``. A
606606
privileged process can change its own Smack label by writing to
607-
/proc/self/attr/current but not the label of another process.
607+
``/proc/self/attr/current`` but not the label of another process.
608+
609+
Format of writing is : only the label or the label followed by one of the
610+
3 trailers: ``\n`` (by common agreement for ``/proc/...`` interfaces),
611+
``\0`` (because some applications incorrectly include it),
612+
``\n\0`` (because we think some applications may incorrectly include it).
608613

609614
File Attribute
610615
~~~~~~~~~~~~~~

security/smack/smack.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,12 @@ int smk_tskacc(struct task_smack *, struct smack_known *,
286286
int smk_curacc(struct smack_known *, u32, struct smk_audit_info *);
287287
int smack_str_from_perm(char *string, int access);
288288
struct smack_known *smack_from_secid(const u32);
289+
int smk_parse_label_len(const char *string, int len);
289290
char *smk_parse_smack(const char *string, int len);
290291
int smk_netlbl_mls(int, char *, struct netlbl_lsm_secattr *, int);
291292
struct smack_known *smk_import_entry(const char *, int);
293+
struct smack_known *smk_import_valid_label(const char *label, int label_len,
294+
gfp_t gfp);
292295
void smk_insert_entry(struct smack_known *skp);
293296
struct smack_known *smk_find_entry(const char *);
294297
bool smack_privileged(int cap);

security/smack/smack_access.c

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -443,34 +443,53 @@ struct smack_known *smk_find_entry(const char *string)
443443
}
444444

445445
/**
446-
* smk_parse_smack - parse smack label from a text string
447-
* @string: a text string that might contain a Smack label
448-
* @len: the maximum size, or zero if it is NULL terminated.
446+
* smk_parse_label_len - calculate the length of the starting segment
447+
* in the string that constitutes a valid smack label
448+
* @string: a text string that might contain a Smack label at the beginning
449+
* @len: the maximum size to look into, may be zero if string is null-terminated
449450
*
450-
* Returns a pointer to the clean label or an error code.
451+
* Returns the length of the segment (0 < L < SMK_LONGLABEL) or an error code.
451452
*/
452-
char *smk_parse_smack(const char *string, int len)
453+
int smk_parse_label_len(const char *string, int len)
453454
{
454-
char *smack;
455455
int i;
456456

457-
if (len <= 0)
458-
len = strlen(string) + 1;
457+
if (len <= 0 || len > SMK_LONGLABEL)
458+
len = SMK_LONGLABEL;
459459

460460
/*
461461
* Reserve a leading '-' as an indicator that
462462
* this isn't a label, but an option to interfaces
463463
* including /smack/cipso and /smack/cipso2
464464
*/
465465
if (string[0] == '-')
466-
return ERR_PTR(-EINVAL);
466+
return -EINVAL;
467467

468468
for (i = 0; i < len; i++)
469469
if (string[i] > '~' || string[i] <= ' ' || string[i] == '/' ||
470470
string[i] == '"' || string[i] == '\\' || string[i] == '\'')
471471
break;
472472

473473
if (i == 0 || i >= SMK_LONGLABEL)
474+
return -EINVAL;
475+
476+
return i;
477+
}
478+
479+
/**
480+
* smk_parse_smack - copy the starting segment in the string
481+
* that constitutes a valid smack label
482+
* @string: a text string that might contain a Smack label at the beginning
483+
* @len: the maximum size to look into, may be zero if string is null-terminated
484+
*
485+
* Returns a pointer to the copy of the label or an error code.
486+
*/
487+
char *smk_parse_smack(const char *string, int len)
488+
{
489+
char *smack;
490+
int i = smk_parse_label_len(string, len);
491+
492+
if (i < 0)
474493
return ERR_PTR(-EINVAL);
475494

476495
smack = kstrndup(string, i, GFP_NOFS);
@@ -554,31 +573,25 @@ int smack_populate_secattr(struct smack_known *skp)
554573
}
555574

556575
/**
557-
* smk_import_entry - import a label, return the list entry
558-
* @string: a text string that might be a Smack label
559-
* @len: the maximum size, or zero if it is NULL terminated.
576+
* smk_import_valid_allocated_label - import a label, return the list entry
577+
* @smack: a text string that is a valid Smack label and may be kfree()ed.
578+
* It is consumed: either becomes a part of the entry or kfree'ed.
560579
*
561-
* Returns a pointer to the entry in the label list that
562-
* matches the passed string, adding it if necessary,
563-
* or an error code.
580+
* Returns: see description of smk_import_entry()
564581
*/
565-
struct smack_known *smk_import_entry(const char *string, int len)
582+
static struct smack_known *
583+
smk_import_allocated_label(char *smack, gfp_t gfp)
566584
{
567585
struct smack_known *skp;
568-
char *smack;
569586
int rc;
570587

571-
smack = smk_parse_smack(string, len);
572-
if (IS_ERR(smack))
573-
return ERR_CAST(smack);
574-
575588
mutex_lock(&smack_known_lock);
576589

577590
skp = smk_find_entry(smack);
578591
if (skp != NULL)
579592
goto freeout;
580593

581-
skp = kzalloc(sizeof(*skp), GFP_NOFS);
594+
skp = kzalloc(sizeof(*skp), gfp);
582595
if (skp == NULL) {
583596
skp = ERR_PTR(-ENOMEM);
584597
goto freeout;
@@ -608,6 +621,42 @@ struct smack_known *smk_import_entry(const char *string, int len)
608621
return skp;
609622
}
610623

624+
/**
625+
* smk_import_entry - import a label, return the list entry
626+
* @string: a text string that might contain a Smack label at the beginning
627+
* @len: the maximum size to look into, may be zero if string is null-terminated
628+
*
629+
* Returns a pointer to the entry in the label list that
630+
* matches the passed string, adding it if necessary,
631+
* or an error code.
632+
*/
633+
struct smack_known *smk_import_entry(const char *string, int len)
634+
{
635+
char *smack = smk_parse_smack(string, len);
636+
637+
if (IS_ERR(smack))
638+
return ERR_CAST(smack);
639+
640+
return smk_import_allocated_label(smack, GFP_NOFS);
641+
}
642+
643+
/**
644+
* smk_import_valid_label - import a label, return the list entry
645+
* @label a text string that is a valid Smack label, not null-terminated
646+
*
647+
* Returns: see description of smk_import_entry()
648+
*/
649+
struct smack_known *
650+
smk_import_valid_label(const char *label, int label_len, gfp_t gfp)
651+
{
652+
char *smack = kstrndup(label, label_len, gfp);
653+
654+
if (!smack)
655+
return ERR_PTR(-ENOMEM);
656+
657+
return smk_import_allocated_label(smack, gfp);
658+
}
659+
611660
/**
612661
* smack_from_secid - find the Smack label associated with a secid
613662
* @secid: an integer that might be associated with a Smack label

security/smack/smack_lsm.c

Lines changed: 74 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3710,7 +3710,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode)
37103710
* @attr: which attribute to fetch
37113711
* @ctx: buffer to receive the result
37123712
* @size: available size in, actual size out
3713-
* @flags: unused
3713+
* @flags: reserved, currently zero
37143714
*
37153715
* Fill the passed user space @ctx with the details of the requested
37163716
* attribute.
@@ -3771,57 +3771,52 @@ static int smack_getprocattr(struct task_struct *p, const char *name, char **val
37713771
* Sets the Smack value of the task. Only setting self
37723772
* is permitted and only with privilege
37733773
*
3774-
* Returns the length of the smack label or an error code
3774+
* Returns zero on success or an error code
37753775
*/
3776-
static int do_setattr(u64 attr, void *value, size_t size)
3776+
static int do_setattr(unsigned int attr, void *value, size_t size)
37773777
{
37783778
struct task_smack *tsp = smack_cred(current_cred());
37793779
struct cred *new;
37803780
struct smack_known *skp;
3781-
char *labelstr;
3782-
int rc = 0;
3783-
3784-
if (!smack_privileged(CAP_MAC_ADMIN) && list_empty(&tsp->smk_relabel))
3785-
return -EPERM;
3781+
int label_len;
37863782

3783+
/*
3784+
* let unprivileged user validate input, check permissions later
3785+
*/
37873786
if (value == NULL || size == 0 || size >= SMK_LONGLABEL)
37883787
return -EINVAL;
37893788

3790-
if (attr != LSM_ATTR_CURRENT)
3791-
return -EOPNOTSUPP;
3792-
3793-
labelstr = smk_parse_smack(value, size);
3794-
if (IS_ERR(labelstr))
3795-
return PTR_ERR(labelstr);
3789+
label_len = smk_parse_label_len(value, size);
3790+
if (label_len < 0 || label_len != size)
3791+
return -EINVAL;
37963792

37973793
/*
37983794
* No process is ever allowed the web ("@") label
37993795
* and the star ("*") label.
38003796
*/
3801-
if (labelstr[1] == '\0' /* '@', '*' */) {
3802-
const char c = labelstr[0];
3797+
if (label_len == 1 /* '@', '*' */) {
3798+
const char c = *(const char *)value;
38033799

38043800
if (c == *smack_known_web.smk_known ||
3805-
c == *smack_known_star.smk_known) {
3806-
rc = -EPERM;
3807-
goto free_labelstr;
3808-
}
3801+
c == *smack_known_star.smk_known)
3802+
return -EPERM;
38093803
}
38103804

38113805
if (!smack_privileged(CAP_MAC_ADMIN)) {
38123806
const struct smack_known_list_elem *sklep;
3813-
list_for_each_entry(sklep, &tsp->smk_relabel, list)
3814-
if (strcmp(sklep->smk_label->smk_known, labelstr) == 0)
3815-
goto free_labelstr;
3816-
rc = -EPERM;
3817-
}
3807+
list_for_each_entry(sklep, &tsp->smk_relabel, list) {
3808+
const char *cp = sklep->smk_label->smk_known;
38183809

3819-
free_labelstr:
3820-
kfree(labelstr);
3821-
if (rc)
3810+
if (strlen(cp) == label_len &&
3811+
strncmp(cp, value, label_len) == 0)
3812+
goto in_relabel;
3813+
}
38223814
return -EPERM;
3815+
in_relabel:
3816+
;
3817+
}
38233818

3824-
skp = smk_import_entry(value, size);
3819+
skp = smk_import_valid_label(value, label_len, GFP_KERNEL);
38253820
if (IS_ERR(skp))
38263821
return PTR_ERR(skp);
38273822

@@ -3837,15 +3832,15 @@ static int do_setattr(u64 attr, void *value, size_t size)
38373832
smk_destroy_label_list(&tsp->smk_relabel);
38383833

38393834
commit_creds(new);
3840-
return size;
3835+
return 0;
38413836
}
38423837

38433838
/**
38443839
* smack_setselfattr - Set a Smack process attribute
38453840
* @attr: which attribute to set
38463841
* @ctx: buffer containing the data
38473842
* @size: size of @ctx
3848-
* @flags: unused
3843+
* @flags: reserved, must be zero
38493844
*
38503845
* Fill the passed user space @ctx with the details of the requested
38513846
* attribute.
@@ -3855,12 +3850,26 @@ static int do_setattr(u64 attr, void *value, size_t size)
38553850
static int smack_setselfattr(unsigned int attr, struct lsm_ctx *ctx,
38563851
u32 size, u32 flags)
38573852
{
3858-
int rc;
3853+
if (attr != LSM_ATTR_CURRENT)
3854+
return -EOPNOTSUPP;
38593855

3860-
rc = do_setattr(attr, ctx->ctx, ctx->ctx_len);
3861-
if (rc > 0)
3862-
return 0;
3863-
return rc;
3856+
if (ctx->flags)
3857+
return -EINVAL;
3858+
/*
3859+
* string must have \0 terminator, included in ctx->ctx
3860+
* (see description of struct lsm_ctx)
3861+
*/
3862+
if (ctx->ctx_len == 0)
3863+
return -EINVAL;
3864+
3865+
if (ctx->ctx[ctx->ctx_len - 1] != '\0')
3866+
return -EINVAL;
3867+
/*
3868+
* other do_setattr() caller, smack_setprocattr(),
3869+
* does not count \0 into size, so
3870+
* decreasing length by 1 to accommodate the divergence.
3871+
*/
3872+
return do_setattr(attr, ctx->ctx, ctx->ctx_len - 1);
38643873
}
38653874

38663875
/**
@@ -3872,15 +3881,39 @@ static int smack_setselfattr(unsigned int attr, struct lsm_ctx *ctx,
38723881
* Sets the Smack value of the task. Only setting self
38733882
* is permitted and only with privilege
38743883
*
3875-
* Returns the length of the smack label or an error code
3884+
* Returns the size of the input value or an error code
38763885
*/
38773886
static int smack_setprocattr(const char *name, void *value, size_t size)
38783887
{
3879-
int attr = lsm_name_to_attr(name);
3888+
size_t realsize = size;
3889+
unsigned int attr = lsm_name_to_attr(name);
38803890

3881-
if (attr != LSM_ATTR_UNDEF)
3882-
return do_setattr(attr, value, size);
3883-
return -EINVAL;
3891+
switch (attr) {
3892+
case LSM_ATTR_UNDEF: return -EINVAL;
3893+
default: return -EOPNOTSUPP;
3894+
case LSM_ATTR_CURRENT:
3895+
;
3896+
}
3897+
3898+
/*
3899+
* The value for the "current" attribute is the label
3900+
* followed by one of the 4 trailers: none, \0, \n, \n\0
3901+
*
3902+
* I.e. following inputs are accepted as 3-characters long label "foo":
3903+
*
3904+
* "foo" (3 characters)
3905+
* "foo\0" (4 characters)
3906+
* "foo\n" (4 characters)
3907+
* "foo\n\0" (5 characters)
3908+
*/
3909+
3910+
if (realsize && (((const char *)value)[realsize - 1] == '\0'))
3911+
--realsize;
3912+
3913+
if (realsize && (((const char *)value)[realsize - 1] == '\n'))
3914+
--realsize;
3915+
3916+
return do_setattr(attr, value, realsize) ? : size;
38843917
}
38853918

38863919
/**

0 commit comments

Comments
 (0)