Skip to content

Commit f9761ad

Browse files
jrjohansengregkh
authored andcommitted
apparmor: fix race on rawdata dereference
commit a0b7091 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 34fc60b commit f9761ad

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

@@ -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);
@@ -1242,18 +1246,17 @@ static const struct file_operations seq_rawdata_ ##NAME ##_fops = { \
12421246
static int seq_rawdata_open(struct inode *inode, struct file *file,
12431247
int (*show)(struct seq_file *, void *))
12441248
{
1245-
struct aa_loaddata *data = __aa_get_loaddata(inode->i_private);
1249+
struct aa_loaddata *data = aa_get_i_loaddata(inode->i_private);
12461250
int error;
12471251

12481252
if (!data)
1249-
/* lost race this ent is being reaped */
12501253
return -ENOENT;
12511254

12521255
error = single_open(file, show, data);
12531256
if (error) {
12541257
AA_BUG(file->private_data &&
12551258
((struct seq_file *)file->private_data)->private);
1256-
aa_put_loaddata(data);
1259+
aa_put_i_loaddata(data);
12571260
}
12581261

12591262
return error;
@@ -1264,7 +1267,7 @@ static int seq_rawdata_release(struct inode *inode, struct file *file)
12641267
struct seq_file *seq = (struct seq_file *) file->private_data;
12651268

12661269
if (seq)
1267-
aa_put_loaddata(seq->private);
1270+
aa_put_i_loaddata(seq->private);
12681271

12691272
return single_release(inode, file);
12701273
}
@@ -1376,9 +1379,8 @@ static int rawdata_open(struct inode *inode, struct file *file)
13761379
if (!aa_current_policy_view_capable(NULL))
13771380
return -EACCES;
13781381

1379-
loaddata = __aa_get_loaddata(inode->i_private);
1382+
loaddata = aa_get_i_loaddata(inode->i_private);
13801383
if (!loaddata)
1381-
/* lost race: this entry is being reaped */
13821384
return -ENOENT;
13831385

13841386
private = rawdata_f_data_alloc(loaddata->size);
@@ -1403,7 +1405,7 @@ static int rawdata_open(struct inode *inode, struct file *file)
14031405
return error;
14041406

14051407
fail_private_alloc:
1406-
aa_put_loaddata(loaddata);
1408+
aa_put_i_loaddata(loaddata);
14071409
return error;
14081410
}
14091411

@@ -1420,9 +1422,9 @@ static void remove_rawdata_dents(struct aa_loaddata *rawdata)
14201422

14211423
for (i = 0; i < AAFS_LOADDATA_NDENTS; i++) {
14221424
if (!IS_ERR_OR_NULL(rawdata->dents[i])) {
1423-
/* no refcounts on i_private */
14241425
aafs_remove(rawdata->dents[i]);
14251426
rawdata->dents[i] = NULL;
1427+
aa_put_i_loaddata(rawdata);
14261428
}
14271429
}
14281430
}
@@ -1461,25 +1463,29 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata)
14611463
if (IS_ERR(dir))
14621464
/* ->name freed when rawdata freed */
14631465
return PTR_ERR(dir);
1466+
aa_get_i_loaddata(rawdata);
14641467
rawdata->dents[AAFS_LOADDATA_DIR] = dir;
14651468

14661469
dent = aafs_create_file("abi", S_IFREG | 0444, dir, rawdata,
14671470
&seq_rawdata_abi_fops);
14681471
if (IS_ERR(dent))
14691472
goto fail;
1473+
aa_get_i_loaddata(rawdata);
14701474
rawdata->dents[AAFS_LOADDATA_ABI] = dent;
14711475

14721476
dent = aafs_create_file("revision", S_IFREG | 0444, dir, rawdata,
14731477
&seq_rawdata_revision_fops);
14741478
if (IS_ERR(dent))
14751479
goto fail;
1480+
aa_get_i_loaddata(rawdata);
14761481
rawdata->dents[AAFS_LOADDATA_REVISION] = dent;
14771482

14781483
if (aa_g_hash_policy) {
14791484
dent = aafs_create_file("sha256", S_IFREG | 0444, dir,
14801485
rawdata, &seq_rawdata_hash_fops);
14811486
if (IS_ERR(dent))
14821487
goto fail;
1488+
aa_get_i_loaddata(rawdata);
14831489
rawdata->dents[AAFS_LOADDATA_HASH] = dent;
14841490
}
14851491

@@ -1488,24 +1494,25 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata)
14881494
&seq_rawdata_compressed_size_fops);
14891495
if (IS_ERR(dent))
14901496
goto fail;
1497+
aa_get_i_loaddata(rawdata);
14911498
rawdata->dents[AAFS_LOADDATA_COMPRESSED_SIZE] = dent;
14921499

14931500
dent = aafs_create_file("raw_data", S_IFREG | 0444,
14941501
dir, rawdata, &rawdata_fops);
14951502
if (IS_ERR(dent))
14961503
goto fail;
1504+
aa_get_i_loaddata(rawdata);
14971505
rawdata->dents[AAFS_LOADDATA_DATA] = dent;
14981506
d_inode(dent)->i_size = rawdata->size;
14991507

15001508
rawdata->ns = aa_get_ns(ns);
15011509
list_add(&rawdata->list, &ns->rawdata_list);
1502-
/* no refcount on inode rawdata */
15031510

15041511
return 0;
15051512

15061513
fail:
15071514
remove_rawdata_dents(rawdata);
1508-
1515+
aa_put_i_loaddata(rawdata);
15091516
return PTR_ERR(dent);
15101517
}
15111518
#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
@@ -338,7 +338,7 @@ void aa_free_profile(struct aa_profile *profile)
338338
}
339339

340340
kfree_sensitive(profile->hash);
341-
aa_put_loaddata(profile->rawdata);
341+
aa_put_profile_loaddata(profile->rawdata);
342342
aa_label_destroy(&profile->label);
343343

344344
kfree_sensitive(profile);
@@ -1123,7 +1123,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
11231123
LIST_HEAD(lh);
11241124

11251125
op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD;
1126-
aa_get_loaddata(udata);
1126+
aa_get_profile_loaddata(udata);
11271127
/* released below */
11281128
error = aa_unpack(udata, &lh, &ns_name);
11291129
if (error)
@@ -1175,10 +1175,10 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
11751175
if (aa_rawdata_eq(rawdata_ent, udata)) {
11761176
struct aa_loaddata *tmp;
11771177

1178-
tmp = __aa_get_loaddata(rawdata_ent);
1178+
tmp = aa_get_profile_loaddata(rawdata_ent);
11791179
/* check we didn't fail the race */
11801180
if (tmp) {
1181-
aa_put_loaddata(udata);
1181+
aa_put_profile_loaddata(udata);
11821182
udata = tmp;
11831183
break;
11841184
}
@@ -1191,7 +1191,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
11911191
struct aa_profile *p;
11921192

11931193
if (aa_g_export_binary)
1194-
ent->new->rawdata = aa_get_loaddata(udata);
1194+
ent->new->rawdata = aa_get_profile_loaddata(udata);
11951195
error = __lookup_replace(ns, ent->new->base.hname,
11961196
!(mask & AA_MAY_REPLACE_POLICY),
11971197
&ent->old, &info);
@@ -1324,7 +1324,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
13241324

13251325
out:
13261326
aa_put_ns(ns);
1327-
aa_put_loaddata(udata);
1327+
aa_put_profile_loaddata(udata);
13281328
kfree(ns_name);
13291329

13301330
if (error)

security/apparmor/policy_unpack.c

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

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

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

133-
void aa_loaddata_kref(struct kref *kref)
146+
void aa_ploaddata_kref(struct kref *kref)
134147
{
135-
struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count);
148+
struct aa_loaddata *d = container_of(kref, struct aa_loaddata, pcount);
136149

137150
if (d) {
138-
INIT_WORK(&d->work, do_loaddata_free);
151+
INIT_WORK(&d->work, do_ploaddata_rmfs);
139152
schedule_work(&d->work);
140153
}
141154
}
@@ -153,6 +166,7 @@ struct aa_loaddata *aa_loaddata_alloc(size_t size)
153166
return ERR_PTR(-ENOMEM);
154167
}
155168
kref_init(&d->count);
169+
kref_init(&d->pcount);
156170
INIT_LIST_HEAD(&d->list);
157171

158172
return d;

0 commit comments

Comments
 (0)