Skip to content

Commit af782cc

Browse files
jrjohansengregkh
authored andcommitted
apparmor: fix race on rawdata dereference
commit a0b7091c4de45a7325c8780e6934a894f92ac86b upstream. There is a race condition that leads to a use-after-free situation: because the rawdata inodes are not refcounted, an attacker can start open()ing one of the rawdata files, and at the same time remove the last reference to this rawdata (by removing the corresponding profile, for example), which frees its struct aa_loaddata; as a result, when seq_rawdata_open() is reached, i_private is a dangling pointer and freed memory is accessed. The rawdata inodes weren't refcounted to avoid a circular refcount and were supposed to be held by the profile rawdata reference. However during profile removal there is a window where the vfs and profile destruction race, resulting in the use after free. Fix this by moving to a double refcount scheme. Where the profile refcount on rawdata is used to break the circular dependency. Allowing for freeing of the rawdata once all inode references to the rawdata are put. Fixes: 5d5182c ("apparmor: move to per loaddata files, instead of replicating in profiles") Reported-by: Qualys Security Advisory <qsa@qualys.com> Reviewed-by: Georgia Garcia <georgia.garcia@canonical.com> Reviewed-by: Maxime Bélair <maxime.belair@canonical.com> Reviewed-by: Cengiz Can <cengiz.can@canonical.com> Tested-by: Salvatore Bonaccorso <carnil@debian.org> Signed-off-by: John Johansen <john.johansen@canonical.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 623a9d2 commit af782cc

File tree

4 files changed

+93
-57
lines changed

4 files changed

+93
-57
lines changed

security/apparmor/apparmorfs.c

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ static void rawdata_f_data_free(struct rawdata_f_data *private)
7979
if (!private)
8080
return;
8181

82-
aa_put_loaddata(private->loaddata);
82+
aa_put_i_loaddata(private->loaddata);
8383
kvfree(private);
8484
}
8585

@@ -404,7 +404,8 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf,
404404

405405
data->size = copy_size;
406406
if (copy_from_user(data->data, userbuf, copy_size)) {
407-
aa_put_loaddata(data);
407+
/* trigger free - don't need to put pcount */
408+
aa_put_i_loaddata(data);
408409
return ERR_PTR(-EFAULT);
409410
}
410411

@@ -432,7 +433,10 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
432433
error = PTR_ERR(data);
433434
if (!IS_ERR(data)) {
434435
error = aa_replace_profiles(ns, label, mask, data);
435-
aa_put_loaddata(data);
436+
/* put pcount, which will put count and free if no
437+
* profiles referencing it.
438+
*/
439+
aa_put_profile_loaddata(data);
436440
}
437441
end_section:
438442
end_current_label_crit_section(label);
@@ -503,7 +507,7 @@ static ssize_t profile_remove(struct file *f, const char __user *buf,
503507
if (!IS_ERR(data)) {
504508
data->data[size] = 0;
505509
error = aa_remove_profiles(ns, label, data->data, size);
506-
aa_put_loaddata(data);
510+
aa_put_profile_loaddata(data);
507511
}
508512
out:
509513
end_current_label_crit_section(label);
@@ -1250,18 +1254,17 @@ static const struct file_operations seq_rawdata_ ##NAME ##_fops = { \
12501254
static int seq_rawdata_open(struct inode *inode, struct file *file,
12511255
int (*show)(struct seq_file *, void *))
12521256
{
1253-
struct aa_loaddata *data = __aa_get_loaddata(inode->i_private);
1257+
struct aa_loaddata *data = aa_get_i_loaddata(inode->i_private);
12541258
int error;
12551259

12561260
if (!data)
1257-
/* lost race this ent is being reaped */
12581261
return -ENOENT;
12591262

12601263
error = single_open(file, show, data);
12611264
if (error) {
12621265
AA_BUG(file->private_data &&
12631266
((struct seq_file *)file->private_data)->private);
1264-
aa_put_loaddata(data);
1267+
aa_put_i_loaddata(data);
12651268
}
12661269

12671270
return error;
@@ -1272,7 +1275,7 @@ static int seq_rawdata_release(struct inode *inode, struct file *file)
12721275
struct seq_file *seq = (struct seq_file *) file->private_data;
12731276

12741277
if (seq)
1275-
aa_put_loaddata(seq->private);
1278+
aa_put_i_loaddata(seq->private);
12761279

12771280
return single_release(inode, file);
12781281
}
@@ -1384,9 +1387,8 @@ static int rawdata_open(struct inode *inode, struct file *file)
13841387
if (!aa_current_policy_view_capable(NULL))
13851388
return -EACCES;
13861389

1387-
loaddata = __aa_get_loaddata(inode->i_private);
1390+
loaddata = aa_get_i_loaddata(inode->i_private);
13881391
if (!loaddata)
1389-
/* lost race: this entry is being reaped */
13901392
return -ENOENT;
13911393

13921394
private = rawdata_f_data_alloc(loaddata->size);
@@ -1411,7 +1413,7 @@ static int rawdata_open(struct inode *inode, struct file *file)
14111413
return error;
14121414

14131415
fail_private_alloc:
1414-
aa_put_loaddata(loaddata);
1416+
aa_put_i_loaddata(loaddata);
14151417
return error;
14161418
}
14171419

@@ -1428,9 +1430,9 @@ static void remove_rawdata_dents(struct aa_loaddata *rawdata)
14281430

14291431
for (i = 0; i < AAFS_LOADDATA_NDENTS; i++) {
14301432
if (!IS_ERR_OR_NULL(rawdata->dents[i])) {
1431-
/* no refcounts on i_private */
14321433
aafs_remove(rawdata->dents[i]);
14331434
rawdata->dents[i] = NULL;
1435+
aa_put_i_loaddata(rawdata);
14341436
}
14351437
}
14361438
}
@@ -1469,25 +1471,29 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata)
14691471
if (IS_ERR(dir))
14701472
/* ->name freed when rawdata freed */
14711473
return PTR_ERR(dir);
1474+
aa_get_i_loaddata(rawdata);
14721475
rawdata->dents[AAFS_LOADDATA_DIR] = dir;
14731476

14741477
dent = aafs_create_file("abi", S_IFREG | 0444, dir, rawdata,
14751478
&seq_rawdata_abi_fops);
14761479
if (IS_ERR(dent))
14771480
goto fail;
1481+
aa_get_i_loaddata(rawdata);
14781482
rawdata->dents[AAFS_LOADDATA_ABI] = dent;
14791483

14801484
dent = aafs_create_file("revision", S_IFREG | 0444, dir, rawdata,
14811485
&seq_rawdata_revision_fops);
14821486
if (IS_ERR(dent))
14831487
goto fail;
1488+
aa_get_i_loaddata(rawdata);
14841489
rawdata->dents[AAFS_LOADDATA_REVISION] = dent;
14851490

14861491
if (aa_g_hash_policy) {
14871492
dent = aafs_create_file("sha256", S_IFREG | 0444, dir,
14881493
rawdata, &seq_rawdata_hash_fops);
14891494
if (IS_ERR(dent))
14901495
goto fail;
1496+
aa_get_i_loaddata(rawdata);
14911497
rawdata->dents[AAFS_LOADDATA_HASH] = dent;
14921498
}
14931499

@@ -1496,24 +1502,25 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata)
14961502
&seq_rawdata_compressed_size_fops);
14971503
if (IS_ERR(dent))
14981504
goto fail;
1505+
aa_get_i_loaddata(rawdata);
14991506
rawdata->dents[AAFS_LOADDATA_COMPRESSED_SIZE] = dent;
15001507

15011508
dent = aafs_create_file("raw_data", S_IFREG | 0444,
15021509
dir, rawdata, &rawdata_fops);
15031510
if (IS_ERR(dent))
15041511
goto fail;
1512+
aa_get_i_loaddata(rawdata);
15051513
rawdata->dents[AAFS_LOADDATA_DATA] = dent;
15061514
d_inode(dent)->i_size = rawdata->size;
15071515

15081516
rawdata->ns = aa_get_ns(ns);
15091517
list_add(&rawdata->list, &ns->rawdata_list);
1510-
/* no refcount on inode rawdata */
15111518

15121519
return 0;
15131520

15141521
fail:
15151522
remove_rawdata_dents(rawdata);
1516-
1523+
aa_put_i_loaddata(rawdata);
15171524
return PTR_ERR(dent);
15181525
}
15191526
#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */

security/apparmor/include/policy_unpack.h

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,29 @@ struct aa_ext {
8787
u32 version;
8888
};
8989

90-
/*
91-
* struct aa_loaddata - buffer of policy raw_data set
90+
/* struct aa_loaddata - buffer of policy raw_data set
91+
* @count: inode/filesystem refcount - use aa_get_i_loaddata()
92+
* @pcount: profile refcount - use aa_get_profile_loaddata()
93+
* @list: list the loaddata is on
94+
* @work: used to do a delayed cleanup
95+
* @dents: refs to dents created in aafs
96+
* @ns: the namespace this loaddata was loaded into
97+
* @name:
98+
* @size: the size of the data that was loaded
99+
* @compressed_size: the size of the data when it is compressed
100+
* @revision: unique revision count that this data was loaded as
101+
* @abi: the abi number the loaddata uses
102+
* @hash: a hash of the loaddata, used to help dedup data
92103
*
93-
* there is no loaddata ref for being on ns list, nor a ref from
94-
* d_inode(@dentry) when grab a ref from these, @ns->lock must be held
95-
* && __aa_get_loaddata() needs to be used, and the return value
96-
* checked, if NULL the loaddata is already being reaped and should be
97-
* considered dead.
104+
* There is no loaddata ref for being on ns->rawdata_list, so
105+
* @ns->lock must be held when walking the list. Dentries and
106+
* inode opens hold refs on @count; profiles hold refs on @pcount.
107+
* When the last @pcount drops, do_ploaddata_rmfs() removes the
108+
* fs entries and drops the associated @count ref.
98109
*/
99110
struct aa_loaddata {
100111
struct kref count;
112+
struct kref pcount;
101113
struct list_head list;
102114
struct work_struct work;
103115
struct dentry *dents[AAFS_LOADDATA_NDENTS];
@@ -119,52 +131,55 @@ struct aa_loaddata {
119131
int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns);
120132

121133
/**
122-
* __aa_get_loaddata - get a reference count to uncounted data reference
134+
* aa_get_loaddata - get a reference count from a counted data reference
123135
* @data: reference to get a count on
124136
*
125-
* Returns: pointer to reference OR NULL if race is lost and reference is
126-
* being repeated.
127-
* Requires: @data->ns->lock held, and the return code MUST be checked
128-
*
129-
* Use only from inode->i_private and @data->list found references
137+
* Returns: pointer to reference
138+
* Requires: @data to have a valid reference count on it. It is a bug
139+
* if the race to reap can be encountered when it is used.
130140
*/
131141
static inline struct aa_loaddata *
132-
__aa_get_loaddata(struct aa_loaddata *data)
142+
aa_get_i_loaddata(struct aa_loaddata *data)
133143
{
134-
if (data && kref_get_unless_zero(&(data->count)))
135-
return data;
136144

137-
return NULL;
145+
if (data)
146+
kref_get(&(data->count));
147+
return data;
138148
}
139149

150+
140151
/**
141-
* aa_get_loaddata - get a reference count from a counted data reference
152+
* aa_get_profile_loaddata - get a profile reference count on loaddata
142153
* @data: reference to get a count on
143154
*
144-
* Returns: point to reference
145-
* Requires: @data to have a valid reference count on it. It is a bug
146-
* if the race to reap can be encountered when it is used.
155+
* Returns: pointer to reference
156+
* Requires: @data to have a valid reference count on it.
147157
*/
148158
static inline struct aa_loaddata *
149-
aa_get_loaddata(struct aa_loaddata *data)
159+
aa_get_profile_loaddata(struct aa_loaddata *data)
150160
{
151-
struct aa_loaddata *tmp = __aa_get_loaddata(data);
152-
153-
AA_BUG(data && !tmp);
154-
155-
return tmp;
161+
if (data)
162+
kref_get(&(data->pcount));
163+
return data;
156164
}
157165

158166
void __aa_loaddata_update(struct aa_loaddata *data, long revision);
159167
bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r);
160168
void aa_loaddata_kref(struct kref *kref);
169+
void aa_ploaddata_kref(struct kref *kref);
161170
struct aa_loaddata *aa_loaddata_alloc(size_t size);
162-
static inline void aa_put_loaddata(struct aa_loaddata *data)
171+
static inline void aa_put_i_loaddata(struct aa_loaddata *data)
163172
{
164173
if (data)
165174
kref_put(&data->count, aa_loaddata_kref);
166175
}
167176

177+
static inline void aa_put_profile_loaddata(struct aa_loaddata *data)
178+
{
179+
if (data)
180+
kref_put(&data->pcount, aa_ploaddata_kref);
181+
}
182+
168183
#if IS_ENABLED(CONFIG_KUNIT)
169184
bool aa_inbounds(struct aa_ext *e, size_t size);
170185
size_t aa_unpack_u16_chunk(struct aa_ext *e, char **chunk);

security/apparmor/policy.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ void aa_free_profile(struct aa_profile *profile)
336336
}
337337

338338
kfree_sensitive(profile->hash);
339-
aa_put_loaddata(profile->rawdata);
339+
aa_put_profile_loaddata(profile->rawdata);
340340
aa_label_destroy(&profile->label);
341341

342342
kfree_sensitive(profile);
@@ -1154,7 +1154,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
11541154
LIST_HEAD(lh);
11551155

11561156
op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD;
1157-
aa_get_loaddata(udata);
1157+
aa_get_profile_loaddata(udata);
11581158
/* released below */
11591159
error = aa_unpack(udata, &lh, &ns_name);
11601160
if (error)
@@ -1206,10 +1206,10 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
12061206
if (aa_rawdata_eq(rawdata_ent, udata)) {
12071207
struct aa_loaddata *tmp;
12081208

1209-
tmp = __aa_get_loaddata(rawdata_ent);
1209+
tmp = aa_get_profile_loaddata(rawdata_ent);
12101210
/* check we didn't fail the race */
12111211
if (tmp) {
1212-
aa_put_loaddata(udata);
1212+
aa_put_profile_loaddata(udata);
12131213
udata = tmp;
12141214
break;
12151215
}
@@ -1222,7 +1222,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
12221222
struct aa_profile *p;
12231223

12241224
if (aa_g_export_binary)
1225-
ent->new->rawdata = aa_get_loaddata(udata);
1225+
ent->new->rawdata = aa_get_profile_loaddata(udata);
12261226
error = __lookup_replace(ns, ent->new->base.hname,
12271227
!(mask & AA_MAY_REPLACE_POLICY),
12281228
&ent->old, &info);
@@ -1355,7 +1355,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
13551355

13561356
out:
13571357
aa_put_ns(ns);
1358-
aa_put_loaddata(udata);
1358+
aa_put_profile_loaddata(udata);
13591359
kfree(ns_name);
13601360

13611361
if (error)

security/apparmor/policy_unpack.c

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,34 +109,47 @@ bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r)
109109
return memcmp(l->data, r->data, r->compressed_size ?: r->size) == 0;
110110
}
111111

112+
static void do_loaddata_free(struct aa_loaddata *d)
113+
{
114+
kfree_sensitive(d->hash);
115+
kfree_sensitive(d->name);
116+
kvfree(d->data);
117+
kfree_sensitive(d);
118+
}
119+
120+
void aa_loaddata_kref(struct kref *kref)
121+
{
122+
struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count);
123+
124+
do_loaddata_free(d);
125+
}
126+
112127
/*
113128
* need to take the ns mutex lock which is NOT safe most places that
114129
* put_loaddata is called, so we have to delay freeing it
115130
*/
116-
static void do_loaddata_free(struct work_struct *work)
131+
static void do_ploaddata_rmfs(struct work_struct *work)
117132
{
118133
struct aa_loaddata *d = container_of(work, struct aa_loaddata, work);
119134
struct aa_ns *ns = aa_get_ns(d->ns);
120135

121136
if (ns) {
122137
mutex_lock_nested(&ns->lock, ns->level);
138+
/* remove fs ref to loaddata */
123139
__aa_fs_remove_rawdata(d);
124140
mutex_unlock(&ns->lock);
125141
aa_put_ns(ns);
126142
}
127-
128-
kfree_sensitive(d->hash);
129-
kfree_sensitive(d->name);
130-
kvfree(d->data);
131-
kfree_sensitive(d);
143+
/* called by dropping last pcount, so drop its associated icount */
144+
aa_put_i_loaddata(d);
132145
}
133146

134-
void aa_loaddata_kref(struct kref *kref)
147+
void aa_ploaddata_kref(struct kref *kref)
135148
{
136-
struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count);
149+
struct aa_loaddata *d = container_of(kref, struct aa_loaddata, pcount);
137150

138151
if (d) {
139-
INIT_WORK(&d->work, do_loaddata_free);
152+
INIT_WORK(&d->work, do_ploaddata_rmfs);
140153
schedule_work(&d->work);
141154
}
142155
}
@@ -154,6 +167,7 @@ struct aa_loaddata *aa_loaddata_alloc(size_t size)
154167
return ERR_PTR(-ENOMEM);
155168
}
156169
kref_init(&d->count);
170+
kref_init(&d->pcount);
157171
INIT_LIST_HEAD(&d->list);
158172

159173
return d;

0 commit comments

Comments
 (0)