Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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: 5d5182cae401 ("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>

+104 -68
+21 -14
security/apparmor/apparmorfs.c
··· 79 79 if (!private) 80 80 return; 81 81 82 - aa_put_loaddata(private->loaddata); 82 + aa_put_i_loaddata(private->loaddata); 83 83 kvfree(private); 84 84 } 85 85 ··· 409 409 410 410 data->size = copy_size; 411 411 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); 413 414 return ERR_PTR(-EFAULT); 414 415 } 415 416 ··· 438 437 error = PTR_ERR(data); 439 438 if (!IS_ERR(data)) { 440 439 error = aa_replace_profiles(ns, label, mask, data); 441 - aa_put_loaddata(data); 440 + /* put pcount, which will put count and free if no 441 + * profiles referencing it. 442 + */ 443 + aa_put_profile_loaddata(data); 442 444 } 443 445 end_section: 444 446 end_current_label_crit_section(label); ··· 512 508 if (!IS_ERR(data)) { 513 509 data->data[size] = 0; 514 510 error = aa_remove_profiles(ns, label, data->data, size); 515 - aa_put_loaddata(data); 511 + aa_put_profile_loaddata(data); 516 512 } 517 513 out: 518 514 end_current_label_crit_section(label); ··· 1259 1255 static int seq_rawdata_open(struct inode *inode, struct file *file, 1260 1256 int (*show)(struct seq_file *, void *)) 1261 1257 { 1262 - struct aa_loaddata *data = __aa_get_loaddata(inode->i_private); 1258 + struct aa_loaddata *data = aa_get_i_loaddata(inode->i_private); 1263 1259 int error; 1264 1260 1265 1261 if (!data) 1266 - /* lost race this ent is being reaped */ 1267 1262 return -ENOENT; 1268 1263 1269 1264 error = single_open(file, show, data); 1270 1265 if (error) { 1271 1266 AA_BUG(file->private_data && 1272 1267 ((struct seq_file *)file->private_data)->private); 1273 - aa_put_loaddata(data); 1268 + aa_put_i_loaddata(data); 1274 1269 } 1275 1270 1276 1271 return error; ··· 1280 1277 struct seq_file *seq = (struct seq_file *) file->private_data; 1281 1278 1282 1279 if (seq) 1283 - aa_put_loaddata(seq->private); 1280 + aa_put_i_loaddata(seq->private); 1284 1281 1285 1282 return single_release(inode, file); 1286 1283 } ··· 1392 1389 if (!aa_current_policy_view_capable(NULL)) 1393 1390 return -EACCES; 1394 1391 1395 - loaddata = __aa_get_loaddata(inode->i_private); 1392 + loaddata = aa_get_i_loaddata(inode->i_private); 1396 1393 if (!loaddata) 1397 - /* lost race: this entry is being reaped */ 1398 1394 return -ENOENT; 1399 1395 1400 1396 private = rawdata_f_data_alloc(loaddata->size); ··· 1418 1416 return error; 1419 1417 1420 1418 fail_private_alloc: 1421 - aa_put_loaddata(loaddata); 1419 + aa_put_i_loaddata(loaddata); 1422 1420 return error; 1423 1421 } 1424 1422 ··· 1435 1433 1436 1434 for (i = 0; i < AAFS_LOADDATA_NDENTS; i++) { 1437 1435 if (!IS_ERR_OR_NULL(rawdata->dents[i])) { 1438 - /* no refcounts on i_private */ 1439 1436 aafs_remove(rawdata->dents[i]); 1440 1437 rawdata->dents[i] = NULL; 1438 + aa_put_i_loaddata(rawdata); 1441 1439 } 1442 1440 } 1443 1441 } ··· 1476 1474 if (IS_ERR(dir)) 1477 1475 /* ->name freed when rawdata freed */ 1478 1476 return PTR_ERR(dir); 1477 + aa_get_i_loaddata(rawdata); 1479 1478 rawdata->dents[AAFS_LOADDATA_DIR] = dir; 1480 1479 1481 1480 dent = aafs_create_file("abi", S_IFREG | 0444, dir, rawdata, 1482 1481 &seq_rawdata_abi_fops); 1483 1482 if (IS_ERR(dent)) 1484 1483 goto fail; 1484 + aa_get_i_loaddata(rawdata); 1485 1485 rawdata->dents[AAFS_LOADDATA_ABI] = dent; 1486 1486 1487 1487 dent = aafs_create_file("revision", S_IFREG | 0444, dir, rawdata, 1488 1488 &seq_rawdata_revision_fops); 1489 1489 if (IS_ERR(dent)) 1490 1490 goto fail; 1491 + aa_get_i_loaddata(rawdata); 1491 1492 rawdata->dents[AAFS_LOADDATA_REVISION] = dent; 1492 1493 1493 1494 if (aa_g_hash_policy) { ··· 1498 1493 rawdata, &seq_rawdata_hash_fops); 1499 1494 if (IS_ERR(dent)) 1500 1495 goto fail; 1496 + aa_get_i_loaddata(rawdata); 1501 1497 rawdata->dents[AAFS_LOADDATA_HASH] = dent; 1502 1498 } 1503 1499 ··· 1507 1501 &seq_rawdata_compressed_size_fops); 1508 1502 if (IS_ERR(dent)) 1509 1503 goto fail; 1504 + aa_get_i_loaddata(rawdata); 1510 1505 rawdata->dents[AAFS_LOADDATA_COMPRESSED_SIZE] = dent; 1511 1506 1512 1507 dent = aafs_create_file("raw_data", S_IFREG | 0444, 1513 1508 dir, rawdata, &rawdata_fops); 1514 1509 if (IS_ERR(dent)) 1515 1510 goto fail; 1511 + aa_get_i_loaddata(rawdata); 1516 1512 rawdata->dents[AAFS_LOADDATA_DATA] = dent; 1517 1513 d_inode(dent)->i_size = rawdata->size; 1518 1514 1519 1515 rawdata->ns = aa_get_ns(ns); 1520 1516 list_add(&rawdata->list, &ns->rawdata_list); 1521 - /* no refcount on inode rawdata */ 1522 1517 1523 1518 return 0; 1524 1519 1525 1520 fail: 1526 1521 remove_rawdata_dents(rawdata); 1527 - 1522 + aa_put_i_loaddata(rawdata); 1528 1523 return PTR_ERR(dent); 1529 1524 } 1530 1525 #endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */
+47 -32
security/apparmor/include/policy_unpack.h
··· 87 87 u32 version; 88 88 }; 89 89 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 92 103 * 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. 98 109 */ 99 110 struct aa_loaddata { 100 111 struct kref count; 112 + struct kref pcount; 101 113 struct list_head list; 102 114 struct work_struct work; 103 115 struct dentry *dents[AAFS_LOADDATA_NDENTS]; ··· 131 119 int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns); 132 120 133 121 /** 134 - * __aa_get_loaddata - get a reference count to uncounted data reference 135 - * @data: reference to get a count on 136 - * 137 - * Returns: pointer to reference OR NULL if race is lost and reference is 138 - * being repeated. 139 - * Requires: @data->ns->lock held, and the return code MUST be checked 140 - * 141 - * Use only from inode->i_private and @data->list found references 142 - */ 143 - static inline struct aa_loaddata * 144 - __aa_get_loaddata(struct aa_loaddata *data) 145 - { 146 - if (data && kref_get_unless_zero(&(data->count))) 147 - return data; 148 - 149 - return NULL; 150 - } 151 - 152 - /** 153 122 * aa_get_loaddata - get a reference count from a counted data reference 154 123 * @data: reference to get a count on 155 124 * 156 - * Returns: point to reference 125 + * Returns: pointer to reference 157 126 * Requires: @data to have a valid reference count on it. It is a bug 158 127 * if the race to reap can be encountered when it is used. 159 128 */ 160 129 static inline struct aa_loaddata * 161 - aa_get_loaddata(struct aa_loaddata *data) 130 + aa_get_i_loaddata(struct aa_loaddata *data) 162 131 { 163 - struct aa_loaddata *tmp = __aa_get_loaddata(data); 164 132 165 - AA_BUG(data && !tmp); 133 + if (data) 134 + kref_get(&(data->count)); 135 + return data; 136 + } 166 137 167 - return tmp; 138 + 139 + /** 140 + * aa_get_profile_loaddata - get a profile reference count on loaddata 141 + * @data: reference to get a count on 142 + * 143 + * Returns: pointer to reference 144 + * Requires: @data to have a valid reference count on it. 145 + */ 146 + static inline struct aa_loaddata * 147 + aa_get_profile_loaddata(struct aa_loaddata *data) 148 + { 149 + if (data) 150 + kref_get(&(data->pcount)); 151 + return data; 168 152 } 169 153 170 154 void __aa_loaddata_update(struct aa_loaddata *data, long revision); 171 155 bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r); 172 156 void aa_loaddata_kref(struct kref *kref); 157 + void aa_ploaddata_kref(struct kref *kref); 173 158 struct aa_loaddata *aa_loaddata_alloc(size_t size); 174 - static inline void aa_put_loaddata(struct aa_loaddata *data) 159 + static inline void aa_put_i_loaddata(struct aa_loaddata *data) 175 160 { 176 161 if (data) 177 162 kref_put(&data->count, aa_loaddata_kref); 163 + } 164 + 165 + static inline void aa_put_profile_loaddata(struct aa_loaddata *data) 166 + { 167 + if (data) 168 + kref_put(&data->pcount, aa_ploaddata_kref); 178 169 } 179 170 180 171 #if IS_ENABLED(CONFIG_KUNIT)
+6 -6
security/apparmor/policy.c
··· 350 350 } 351 351 352 352 kfree_sensitive(profile->hash); 353 - aa_put_loaddata(profile->rawdata); 353 + aa_put_profile_loaddata(profile->rawdata); 354 354 aa_label_destroy(&profile->label); 355 355 356 356 kfree_sensitive(profile); ··· 1171 1171 LIST_HEAD(lh); 1172 1172 1173 1173 op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD; 1174 - aa_get_loaddata(udata); 1174 + aa_get_profile_loaddata(udata); 1175 1175 /* released below */ 1176 1176 error = aa_unpack(udata, &lh, &ns_name); 1177 1177 if (error) ··· 1223 1223 if (aa_rawdata_eq(rawdata_ent, udata)) { 1224 1224 struct aa_loaddata *tmp; 1225 1225 1226 - tmp = __aa_get_loaddata(rawdata_ent); 1226 + tmp = aa_get_profile_loaddata(rawdata_ent); 1227 1227 /* check we didn't fail the race */ 1228 1228 if (tmp) { 1229 - aa_put_loaddata(udata); 1229 + aa_put_profile_loaddata(udata); 1230 1230 udata = tmp; 1231 1231 break; 1232 1232 } ··· 1239 1239 struct aa_profile *p; 1240 1240 1241 1241 if (aa_g_export_binary) 1242 - ent->new->rawdata = aa_get_loaddata(udata); 1242 + ent->new->rawdata = aa_get_profile_loaddata(udata); 1243 1243 error = __lookup_replace(ns, ent->new->base.hname, 1244 1244 !(mask & AA_MAY_REPLACE_POLICY), 1245 1245 &ent->old, &info); ··· 1372 1372 1373 1373 out: 1374 1374 aa_put_ns(ns); 1375 - aa_put_loaddata(udata); 1375 + aa_put_profile_loaddata(udata); 1376 1376 kfree(ns_name); 1377 1377 1378 1378 if (error)
+30 -16
security/apparmor/policy_unpack.c
··· 109 109 return memcmp(l->data, r->data, r->compressed_size ?: r->size) == 0; 110 110 } 111 111 112 - /* 113 - * need to take the ns mutex lock which is NOT safe most places that 114 - * put_loaddata is called, so we have to delay freeing it 115 - */ 116 - static void do_loaddata_free(struct work_struct *work) 112 + static void do_loaddata_free(struct aa_loaddata *d) 117 113 { 118 - struct aa_loaddata *d = container_of(work, struct aa_loaddata, work); 119 - struct aa_ns *ns = aa_get_ns(d->ns); 120 - 121 - if (ns) { 122 - mutex_lock_nested(&ns->lock, ns->level); 123 - __aa_fs_remove_rawdata(d); 124 - mutex_unlock(&ns->lock); 125 - aa_put_ns(ns); 126 - } 127 - 128 114 kfree_sensitive(d->hash); 129 115 kfree_sensitive(d->name); 130 116 kvfree(d->data); ··· 121 135 { 122 136 struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count); 123 137 138 + do_loaddata_free(d); 139 + } 140 + 141 + /* 142 + * need to take the ns mutex lock which is NOT safe most places that 143 + * put_loaddata is called, so we have to delay freeing it 144 + */ 145 + static void do_ploaddata_rmfs(struct work_struct *work) 146 + { 147 + struct aa_loaddata *d = container_of(work, struct aa_loaddata, work); 148 + struct aa_ns *ns = aa_get_ns(d->ns); 149 + 150 + if (ns) { 151 + mutex_lock_nested(&ns->lock, ns->level); 152 + /* remove fs ref to loaddata */ 153 + __aa_fs_remove_rawdata(d); 154 + mutex_unlock(&ns->lock); 155 + aa_put_ns(ns); 156 + } 157 + /* called by dropping last pcount, so drop its associated icount */ 158 + aa_put_i_loaddata(d); 159 + } 160 + 161 + void aa_ploaddata_kref(struct kref *kref) 162 + { 163 + struct aa_loaddata *d = container_of(kref, struct aa_loaddata, pcount); 164 + 124 165 if (d) { 125 - INIT_WORK(&d->work, do_loaddata_free); 166 + INIT_WORK(&d->work, do_ploaddata_rmfs); 126 167 schedule_work(&d->work); 127 168 } 128 169 } ··· 167 154 return ERR_PTR(-ENOMEM); 168 155 } 169 156 kref_init(&d->count); 157 + kref_init(&d->pcount); 170 158 INIT_LIST_HEAD(&d->list); 171 159 172 160 return d;