Skip to content

Commit a0b7091

Browse files
committed
apparmor: fix race on rawdata dereference
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>
1 parent 39440b1 commit a0b7091

4 files changed

Lines changed: 93 additions & 57 deletions

File tree

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

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

410410
data->size = copy_size;
411411
if (copy_from_user(data->data, userbuf, copy_size)) {
412-
aa_put_loaddata(data);
412+
/* trigger free - don't need to put pcount */
413+
aa_put_i_loaddata(data);
413414
return ERR_PTR(-EFAULT);
414415
}
415416

@@ -437,7 +438,10 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
437438
error = PTR_ERR(data);
438439
if (!IS_ERR(data)) {
439440
error = aa_replace_profiles(ns, label, mask, data);
440-
aa_put_loaddata(data);
441+
/* put pcount, which will put count and free if no
442+
* profiles referencing it.
443+
*/
444+
aa_put_profile_loaddata(data);
441445
}
442446
end_section:
443447
end_current_label_crit_section(label);
@@ -508,7 +512,7 @@ static ssize_t profile_remove(struct file *f, const char __user *buf,
508512
if (!IS_ERR(data)) {
509513
data->data[size] = 0;
510514
error = aa_remove_profiles(ns, label, data->data, size);
511-
aa_put_loaddata(data);
515+
aa_put_profile_loaddata(data);
512516
}
513517
out:
514518
end_current_label_crit_section(label);
@@ -1255,18 +1259,17 @@ static const struct file_operations seq_rawdata_ ##NAME ##_fops = { \
12551259
static int seq_rawdata_open(struct inode *inode, struct file *file,
12561260
int (*show)(struct seq_file *, void *))
12571261
{
1258-
struct aa_loaddata *data = __aa_get_loaddata(inode->i_private);
1262+
struct aa_loaddata *data = aa_get_i_loaddata(inode->i_private);
12591263
int error;
12601264

12611265
if (!data)
1262-
/* lost race this ent is being reaped */
12631266
return -ENOENT;
12641267

12651268
error = single_open(file, show, data);
12661269
if (error) {
12671270
AA_BUG(file->private_data &&
12681271
((struct seq_file *)file->private_data)->private);
1269-
aa_put_loaddata(data);
1272+
aa_put_i_loaddata(data);
12701273
}
12711274

12721275
return error;
@@ -1277,7 +1280,7 @@ static int seq_rawdata_release(struct inode *inode, struct file *file)
12771280
struct seq_file *seq = (struct seq_file *) file->private_data;
12781281

12791282
if (seq)
1280-
aa_put_loaddata(seq->private);
1283+
aa_put_i_loaddata(seq->private);
12811284

12821285
return single_release(inode, file);
12831286
}
@@ -1389,9 +1392,8 @@ static int rawdata_open(struct inode *inode, struct file *file)
13891392
if (!aa_current_policy_view_capable(NULL))
13901393
return -EACCES;
13911394

1392-
loaddata = __aa_get_loaddata(inode->i_private);
1395+
loaddata = aa_get_i_loaddata(inode->i_private);
13931396
if (!loaddata)
1394-
/* lost race: this entry is being reaped */
13951397
return -ENOENT;
13961398

13971399
private = rawdata_f_data_alloc(loaddata->size);
@@ -1416,7 +1418,7 @@ static int rawdata_open(struct inode *inode, struct file *file)
14161418
return error;
14171419

14181420
fail_private_alloc:
1419-
aa_put_loaddata(loaddata);
1421+
aa_put_i_loaddata(loaddata);
14201422
return error;
14211423
}
14221424

@@ -1433,9 +1435,9 @@ static void remove_rawdata_dents(struct aa_loaddata *rawdata)
14331435

14341436
for (i = 0; i < AAFS_LOADDATA_NDENTS; i++) {
14351437
if (!IS_ERR_OR_NULL(rawdata->dents[i])) {
1436-
/* no refcounts on i_private */
14371438
aafs_remove(rawdata->dents[i]);
14381439
rawdata->dents[i] = NULL;
1440+
aa_put_i_loaddata(rawdata);
14391441
}
14401442
}
14411443
}
@@ -1474,25 +1476,29 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata)
14741476
if (IS_ERR(dir))
14751477
/* ->name freed when rawdata freed */
14761478
return PTR_ERR(dir);
1479+
aa_get_i_loaddata(rawdata);
14771480
rawdata->dents[AAFS_LOADDATA_DIR] = dir;
14781481

14791482
dent = aafs_create_file("abi", S_IFREG | 0444, dir, rawdata,
14801483
&seq_rawdata_abi_fops);
14811484
if (IS_ERR(dent))
14821485
goto fail;
1486+
aa_get_i_loaddata(rawdata);
14831487
rawdata->dents[AAFS_LOADDATA_ABI] = dent;
14841488

14851489
dent = aafs_create_file("revision", S_IFREG | 0444, dir, rawdata,
14861490
&seq_rawdata_revision_fops);
14871491
if (IS_ERR(dent))
14881492
goto fail;
1493+
aa_get_i_loaddata(rawdata);
14891494
rawdata->dents[AAFS_LOADDATA_REVISION] = dent;
14901495

14911496
if (aa_g_hash_policy) {
14921497
dent = aafs_create_file("sha256", S_IFREG | 0444, dir,
14931498
rawdata, &seq_rawdata_hash_fops);
14941499
if (IS_ERR(dent))
14951500
goto fail;
1501+
aa_get_i_loaddata(rawdata);
14961502
rawdata->dents[AAFS_LOADDATA_HASH] = dent;
14971503
}
14981504

@@ -1501,24 +1507,25 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata)
15011507
&seq_rawdata_compressed_size_fops);
15021508
if (IS_ERR(dent))
15031509
goto fail;
1510+
aa_get_i_loaddata(rawdata);
15041511
rawdata->dents[AAFS_LOADDATA_COMPRESSED_SIZE] = dent;
15051512

15061513
dent = aafs_create_file("raw_data", S_IFREG | 0444,
15071514
dir, rawdata, &rawdata_fops);
15081515
if (IS_ERR(dent))
15091516
goto fail;
1517+
aa_get_i_loaddata(rawdata);
15101518
rawdata->dents[AAFS_LOADDATA_DATA] = dent;
15111519
d_inode(dent)->i_size = rawdata->size;
15121520

15131521
rawdata->ns = aa_get_ns(ns);
15141522
list_add(&rawdata->list, &ns->rawdata_list);
1515-
/* no refcount on inode rawdata */
15161523

15171524
return 0;
15181525

15191526
fail:
15201527
remove_rawdata_dents(rawdata);
1521-
1528+
aa_put_i_loaddata(rawdata);
15221529
return PTR_ERR(dent);
15231530
}
15241531
#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
@@ -350,7 +350,7 @@ void aa_free_profile(struct aa_profile *profile)
350350
}
351351

352352
kfree_sensitive(profile->hash);
353-
aa_put_loaddata(profile->rawdata);
353+
aa_put_profile_loaddata(profile->rawdata);
354354
aa_label_destroy(&profile->label);
355355

356356
kfree_sensitive(profile);
@@ -1171,7 +1171,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
11711171
LIST_HEAD(lh);
11721172

11731173
op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD;
1174-
aa_get_loaddata(udata);
1174+
aa_get_profile_loaddata(udata);
11751175
/* released below */
11761176
error = aa_unpack(udata, &lh, &ns_name);
11771177
if (error)
@@ -1223,10 +1223,10 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
12231223
if (aa_rawdata_eq(rawdata_ent, udata)) {
12241224
struct aa_loaddata *tmp;
12251225

1226-
tmp = __aa_get_loaddata(rawdata_ent);
1226+
tmp = aa_get_profile_loaddata(rawdata_ent);
12271227
/* check we didn't fail the race */
12281228
if (tmp) {
1229-
aa_put_loaddata(udata);
1229+
aa_put_profile_loaddata(udata);
12301230
udata = tmp;
12311231
break;
12321232
}
@@ -1239,7 +1239,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
12391239
struct aa_profile *p;
12401240

12411241
if (aa_g_export_binary)
1242-
ent->new->rawdata = aa_get_loaddata(udata);
1242+
ent->new->rawdata = aa_get_profile_loaddata(udata);
12431243
error = __lookup_replace(ns, ent->new->base.hname,
12441244
!(mask & AA_MAY_REPLACE_POLICY),
12451245
&ent->old, &info);
@@ -1372,7 +1372,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
13721372

13731373
out:
13741374
aa_put_ns(ns);
1375-
aa_put_loaddata(udata);
1375+
aa_put_profile_loaddata(udata);
13761376
kfree(ns_name);
13771377

13781378
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)