Commit 77b071b34045a0c65d0e1f85f3d47fd2b8b7a8a1

Authored by John Johansen
1 parent 01e2b670aa

apparmor: change how profile replacement update is done

remove the use of replaced by chaining and move to profile invalidation
and lookup to handle task replacement.

Replacement chaining can result in large chains of profiles being pinned
in memory when one profile in the chain is use. With implicit labeling
this will be even more of a problem, so move to a direct lookup method.

Signed-off-by: John Johansen <john.johansen@canonical.com>

Showing 6 changed files with 125 additions and 87 deletions Side-by-side Diff

security/apparmor/context.c
... ... @@ -112,9 +112,9 @@
112 112 aa_clear_task_cxt_trans(cxt);
113 113  
114 114 /* be careful switching cxt->profile, when racing replacement it
115   - * is possible that cxt->profile->replacedby is the reference keeping
116   - * @profile valid, so make sure to get its reference before dropping
117   - * the reference on cxt->profile */
  115 + * is possible that cxt->profile->replacedby->profile is the reference
  116 + * keeping @profile valid, so make sure to get its reference before
  117 + * dropping the reference on cxt->profile */
118 118 aa_get_profile(profile);
119 119 aa_put_profile(cxt->profile);
120 120 cxt->profile = profile;
... ... @@ -175,7 +175,7 @@
175 175 abort_creds(new);
176 176 return -EACCES;
177 177 }
178   - cxt->profile = aa_get_profile(aa_newest_version(profile));
  178 + cxt->profile = aa_get_newest_profile(profile);
179 179 /* clear exec on switching context */
180 180 aa_put_profile(cxt->onexec);
181 181 cxt->onexec = NULL;
182 182  
... ... @@ -212,14 +212,8 @@
212 212 }
213 213  
214 214 aa_put_profile(cxt->profile);
215   - cxt->profile = aa_newest_version(cxt->previous);
  215 + cxt->profile = aa_get_newest_profile(cxt->previous);
216 216 BUG_ON(!cxt->profile);
217   - if (unlikely(cxt->profile != cxt->previous)) {
218   - aa_get_profile(cxt->profile);
219   - aa_put_profile(cxt->previous);
220   - }
221   - /* ref has been transfered so avoid putting ref in clear_task_cxt */
222   - cxt->previous = NULL;
223 217 /* clear exec && prev information when restoring to previous context */
224 218 aa_clear_task_cxt_trans(cxt);
225 219  
security/apparmor/domain.c
... ... @@ -359,7 +359,7 @@
359 359 cxt = cred_cxt(bprm->cred);
360 360 BUG_ON(!cxt);
361 361  
362   - profile = aa_get_profile(aa_newest_version(cxt->profile));
  362 + profile = aa_get_newest_profile(cxt->profile);
363 363 /*
364 364 * get the namespace from the replacement profile as replacement
365 365 * can change the namespace
... ... @@ -417,7 +417,7 @@
417 417  
418 418 if (!(cp.allow & AA_MAY_ONEXEC))
419 419 goto audit;
420   - new_profile = aa_get_profile(aa_newest_version(cxt->onexec));
  420 + new_profile = aa_get_newest_profile(cxt->onexec);
421 421 goto apply;
422 422 }
423 423  
security/apparmor/include/context.h
... ... @@ -98,7 +98,7 @@
98 98 {
99 99 struct aa_task_cxt *cxt = cred_cxt(cred);
100 100 BUG_ON(!cxt || !cxt->profile);
101   - return aa_newest_version(cxt->profile);
  101 + return cxt->profile;
102 102 }
103 103  
104 104 /**
105 105  
106 106  
... ... @@ -152,15 +152,14 @@
152 152 struct aa_profile *profile;
153 153 BUG_ON(!cxt || !cxt->profile);
154 154  
155   - profile = aa_newest_version(cxt->profile);
156   - /*
157   - * Whether or not replacement succeeds, use newest profile so
158   - * there is no need to update it after replacement.
159   - */
160   - if (unlikely((cxt->profile != profile)))
  155 + if (PROFILE_INVALID(cxt->profile)) {
  156 + profile = aa_get_newest_profile(cxt->profile);
161 157 aa_replace_current_profile(profile);
  158 + aa_put_profile(profile);
  159 + cxt = current_cxt();
  160 + }
162 161  
163   - return profile;
  162 + return cxt->profile;
164 163 }
165 164  
166 165 /**
security/apparmor/include/policy.h
... ... @@ -42,6 +42,8 @@
42 42  
43 43 #define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT)
44 44  
  45 +#define PROFILE_INVALID(_profile) ((_profile)->flags & PFLAG_INVALID)
  46 +
45 47 #define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2)
46 48  
47 49 /*
... ... @@ -65,6 +67,7 @@
65 67 PFLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */
66 68 PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */
67 69 PFLAG_OLD_NULL_TRANS = 0x100, /* use // as the null transition */
  70 + PFLAG_INVALID = 0x200, /* profile replaced/removed */
68 71  
69 72 /* These flags must correspond with PATH_flags */
70 73 PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */
... ... @@ -146,6 +149,12 @@
146 149  
147 150 };
148 151  
  152 +struct aa_replacedby {
  153 + struct kref count;
  154 + struct aa_profile __rcu *profile;
  155 +};
  156 +
  157 +
149 158 /* struct aa_profile - basic confinement data
150 159 * @base - base components of the profile (name, refcount, lists, lock ...)
151 160 * @parent: parent of profile
... ... @@ -169,8 +178,7 @@
169 178 * used to determine profile attachment against unconfined tasks. All other
170 179 * attachments are determined by profile X transition rules.
171 180 *
172   - * The @replacedby field is write protected by the profile lock. Reads
173   - * are assumed to be atomic.
  181 + * The @replacedby struct is write protected by the profile lock.
174 182 *
175 183 * Profiles have a hierarchy where hats and children profiles keep
176 184 * a reference to their parent.
177 185  
... ... @@ -184,14 +192,14 @@
184 192 struct aa_profile __rcu *parent;
185 193  
186 194 struct aa_namespace *ns;
187   - struct aa_profile *replacedby;
  195 + struct aa_replacedby *replacedby;
188 196 const char *rename;
189 197  
190 198 struct aa_dfa *xmatch;
191 199 int xmatch_len;
192 200 enum audit_mode audit;
193 201 enum profile_mode mode;
194   - u32 flags;
  202 + long flags;
195 203 u32 path_flags;
196 204 int size;
197 205  
... ... @@ -250,6 +258,7 @@
250 258 kref_put(&ns->base.count, aa_free_namespace_kref);
251 259 }
252 260  
  261 +void aa_free_replacedby_kref(struct kref *kref);
253 262 struct aa_profile *aa_alloc_profile(const char *name);
254 263 struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat);
255 264 void aa_free_profile_kref(struct kref *kref);
256 265  
... ... @@ -265,25 +274,7 @@
265 274  
266 275 #define unconfined(X) ((X)->flags & PFLAG_UNCONFINED)
267 276  
268   -/**
269   - * aa_newest_version - find the newest version of @profile
270   - * @profile: the profile to check for newer versions of (NOT NULL)
271   - *
272   - * Returns: newest version of @profile, if @profile is the newest version
273   - * return @profile.
274   - *
275   - * NOTE: the profile returned is not refcounted, The refcount on @profile
276   - * must be held until the caller decides what to do with the returned newest
277   - * version.
278   - */
279   -static inline struct aa_profile *aa_newest_version(struct aa_profile *profile)
280   -{
281   - while (profile->replacedby)
282   - profile = profile->replacedby;
283 277  
284   - return profile;
285   -}
286   -
287 278 /**
288 279 * aa_get_profile - increment refcount on profile @p
289 280 * @p: profile (MAYBE NULL)
... ... @@ -335,6 +326,25 @@
335 326 }
336 327  
337 328 /**
  329 + * aa_get_newest_profile - find the newest version of @profile
  330 + * @profile: the profile to check for newer versions of
  331 + *
  332 + * Returns: refcounted newest version of @profile taking into account
  333 + * replacement, renames and removals
  334 + * return @profile.
  335 + */
  336 +static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
  337 +{
  338 + if (!p)
  339 + return NULL;
  340 +
  341 + if (PROFILE_INVALID(p))
  342 + return aa_get_profile_rcu(&p->replacedby->profile);
  343 +
  344 + return aa_get_profile(p);
  345 +}
  346 +
  347 +/**
338 348 * aa_put_profile - decrement refcount on profile @p
339 349 * @p: profile (MAYBE NULL)
340 350 */
... ... @@ -342,6 +352,30 @@
342 352 {
343 353 if (p)
344 354 kref_put(&p->base.count, aa_free_profile_kref);
  355 +}
  356 +
  357 +static inline struct aa_replacedby *aa_get_replacedby(struct aa_replacedby *p)
  358 +{
  359 + if (p)
  360 + kref_get(&(p->count));
  361 +
  362 + return p;
  363 +}
  364 +
  365 +static inline void aa_put_replacedby(struct aa_replacedby *p)
  366 +{
  367 + if (p)
  368 + kref_put(&p->count, aa_free_replacedby_kref);
  369 +}
  370 +
  371 +/* requires profile list write lock held */
  372 +static inline void __aa_update_replacedby(struct aa_profile *orig,
  373 + struct aa_profile *new)
  374 +{
  375 + struct aa_profile *tmp = rcu_dereference(orig->replacedby->profile);
  376 + rcu_assign_pointer(orig->replacedby->profile, aa_get_profile(new));
  377 + orig->flags |= PFLAG_INVALID;
  378 + aa_put_profile(tmp);
345 379 }
346 380  
347 381 static inline int AUDIT_MODE(struct aa_profile *profile)
security/apparmor/lsm.c
... ... @@ -508,19 +508,21 @@
508 508 /* released below */
509 509 const struct cred *cred = get_task_cred(task);
510 510 struct aa_task_cxt *cxt = cred_cxt(cred);
  511 + struct aa_profile *profile = NULL;
511 512  
512 513 if (strcmp(name, "current") == 0)
513   - error = aa_getprocattr(aa_newest_version(cxt->profile),
514   - value);
  514 + profile = aa_get_newest_profile(cxt->profile);
515 515 else if (strcmp(name, "prev") == 0 && cxt->previous)
516   - error = aa_getprocattr(aa_newest_version(cxt->previous),
517   - value);
  516 + profile = aa_get_newest_profile(cxt->previous);
518 517 else if (strcmp(name, "exec") == 0 && cxt->onexec)
519   - error = aa_getprocattr(aa_newest_version(cxt->onexec),
520   - value);
  518 + profile = aa_get_newest_profile(cxt->onexec);
521 519 else
522 520 error = -EINVAL;
523 521  
  522 + if (profile)
  523 + error = aa_getprocattr(profile, value);
  524 +
  525 + aa_put_profile(profile);
524 526 put_cred(cred);
525 527  
526 528 return error;
security/apparmor/policy.c
... ... @@ -469,7 +469,7 @@
469 469 /* release any children lists first */
470 470 __profile_list_release(&profile->base.profiles);
471 471 /* released by free_profile */
472   - profile->replacedby = aa_get_profile(profile->ns->unconfined);
  472 + __aa_update_replacedby(profile, profile->ns->unconfined);
473 473 __list_remove_profile(profile);
474 474 }
475 475  
... ... @@ -588,6 +588,23 @@
588 588 aa_put_namespace(ns);
589 589 }
590 590  
  591 +
  592 +static void free_replacedby(struct aa_replacedby *r)
  593 +{
  594 + if (r) {
  595 + aa_put_profile(rcu_dereference(r->profile));
  596 + kzfree(r);
  597 + }
  598 +}
  599 +
  600 +
  601 +void aa_free_replacedby_kref(struct kref *kref)
  602 +{
  603 + struct aa_replacedby *r = container_of(kref, struct aa_replacedby,
  604 + count);
  605 + free_replacedby(r);
  606 +}
  607 +
591 608 /**
592 609 * free_profile - free a profile
593 610 * @profile: the profile to free (MAYBE NULL)
... ... @@ -600,8 +617,6 @@
600 617 */
601 618 static void free_profile(struct aa_profile *profile)
602 619 {
603   - struct aa_profile *p;
604   -
605 620 AA_DEBUG("%s(%p)\n", __func__, profile);
606 621  
607 622 if (!profile)
608 623  
... ... @@ -620,29 +635,8 @@
620 635  
621 636 aa_put_dfa(profile->xmatch);
622 637 aa_put_dfa(profile->policy.dfa);
  638 + aa_put_replacedby(profile->replacedby);
623 639  
624   - /* put the profile reference for replacedby, but not via
625   - * put_profile(kref_put).
626   - * replacedby can form a long chain that can result in cascading
627   - * frees that blows the stack because kref_put makes a nested fn
628   - * call (it looks like recursion, with free_profile calling
629   - * free_profile) for each profile in the chain lp#1056078.
630   - */
631   - for (p = profile->replacedby; p; ) {
632   - if (atomic_dec_and_test(&p->base.count.refcount)) {
633   - /* no more refs on p, grab its replacedby */
634   - struct aa_profile *next = p->replacedby;
635   - /* break the chain */
636   - p->replacedby = NULL;
637   - /* now free p, chain is broken */
638   - free_profile(p);
639   -
640   - /* follow up with next profile in the chain */
641   - p = next;
642   - } else
643   - break;
644   - }
645   -
646 640 kzfree(profile);
647 641 }
648 642  
649 643  
650 644  
... ... @@ -682,13 +676,22 @@
682 676 if (!profile)
683 677 return NULL;
684 678  
685   - if (!policy_init(&profile->base, NULL, hname)) {
686   - kzfree(profile);
687   - return NULL;
688   - }
  679 + profile->replacedby = kzalloc(sizeof(struct aa_replacedby), GFP_KERNEL);
  680 + if (!profile->replacedby)
  681 + goto fail;
  682 + kref_init(&profile->replacedby->count);
689 683  
  684 + if (!policy_init(&profile->base, NULL, hname))
  685 + goto fail;
  686 +
690 687 /* refcount released by caller */
691 688 return profile;
  689 +
  690 +fail:
  691 + kzfree(profile->replacedby);
  692 + kzfree(profile);
  693 +
  694 + return NULL;
692 695 }
693 696  
694 697 /**
... ... @@ -985,6 +988,7 @@
985 988 * __replace_profile - replace @old with @new on a list
986 989 * @old: profile to be replaced (NOT NULL)
987 990 * @new: profile to replace @old with (NOT NULL)
  991 + * @share_replacedby: transfer @old->replacedby to @new
988 992 *
989 993 * Will duplicate and refcount elements that @new inherits from @old
990 994 * and will inherit @old children.
... ... @@ -993,7 +997,8 @@
993 997 *
994 998 * Requires: namespace list lock be held, or list not be shared
995 999 */
996   -static void __replace_profile(struct aa_profile *old, struct aa_profile *new)
  1000 +static void __replace_profile(struct aa_profile *old, struct aa_profile *new,
  1001 + bool share_replacedby)
997 1002 {
998 1003 struct aa_profile *child, *tmp;
999 1004  
... ... @@ -1008,7 +1013,7 @@
1008 1013 p = __find_child(&new->base.profiles, child->base.name);
1009 1014 if (p) {
1010 1015 /* @p replaces @child */
1011   - __replace_profile(child, p);
  1016 + __replace_profile(child, p, share_replacedby);
1012 1017 continue;
1013 1018 }
1014 1019  
... ... @@ -1027,8 +1032,11 @@
1027 1032 struct aa_profile *parent = rcu_dereference(old->parent);
1028 1033 rcu_assign_pointer(new->parent, aa_get_profile(parent));
1029 1034 }
1030   - /* released by free_profile */
1031   - old->replacedby = aa_get_profile(new);
  1035 + __aa_update_replacedby(old, new);
  1036 + if (share_replacedby) {
  1037 + aa_put_replacedby(new->replacedby);
  1038 + new->replacedby = aa_get_replacedby(old->replacedby);
  1039 + }
1032 1040  
1033 1041 if (list_empty(&new->base.list)) {
1034 1042 /* new is not on a list already */
1035 1043  
1036 1044  
1037 1045  
1038 1046  
... ... @@ -1152,23 +1160,24 @@
1152 1160 audit_policy(op, GFP_ATOMIC, ent->new->base.name, NULL, error);
1153 1161  
1154 1162 if (ent->old) {
1155   - __replace_profile(ent->old, ent->new);
  1163 + __replace_profile(ent->old, ent->new, 1);
1156 1164 if (ent->rename)
1157   - __replace_profile(ent->rename, ent->new);
  1165 + __replace_profile(ent->rename, ent->new, 0);
1158 1166 } else if (ent->rename) {
1159   - __replace_profile(ent->rename, ent->new);
  1167 + __replace_profile(ent->rename, ent->new, 0);
1160 1168 } else if (ent->new->parent) {
1161 1169 struct aa_profile *parent, *newest;
1162 1170 parent = rcu_dereference_protected(ent->new->parent,
1163 1171 mutex_is_locked(&ns->lock));
1164   - newest = aa_newest_version(parent);
  1172 + newest = aa_get_newest_profile(parent);
1165 1173  
1166 1174 /* parent replaced in this atomic set? */
1167 1175 if (newest != parent) {
1168 1176 aa_get_profile(newest);
1169 1177 aa_put_profile(parent);
1170 1178 rcu_assign_pointer(ent->new->parent, newest);
1171   - }
  1179 + } else
  1180 + aa_put_profile(newest);
1172 1181 __list_add_profile(&parent->base.profiles, ent->new);
1173 1182 } else
1174 1183 __list_add_profile(&ns->base.profiles, ent->new);