apparmor: fix race in unix socket mediation when peer_path is used

The holding a reference to the peer_sk is not enough to ensure access
to the peer sk path. Accessing the path outside of the state lock
allows for a race with unix_release_sock(). Fix this by taking the
state lock and getting a reference to the path under lock.

Ideally for connected sockets we would cache this information so we
don't have to take the lock here. But for now just fix the race.

Fixes: bc6e5f6933 ("apparmor: Remove use of the double lock")
Signed-off-by: John Johansen <john.johansen@canonical.com>
This commit is contained in:
John Johansen 2025-10-24 12:25:38 -07:00
parent 4483efe4f2
commit b1aea2c196

View File

@ -748,41 +748,47 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label,
if (!peer_sk)
goto out;
peer_addr = aa_sunaddr(unix_sk(peer_sk), &peer_addrlen);
if (!is_sk_fs) {
bool is_peer_fs = is_unix_fs(peer_sk);
struct path peer_path;
peer_addr = aa_sunaddr(unix_sk(peer_sk), &peer_addrlen);
if (is_peer_fs) {
struct path peer_path;
peer_path = unix_sk(peer_sk)->path;
if (!is_sk_fs && is_unix_fs(peer_sk)) {
last_error(error,
unix_fs_perm(op, request, subj_cred, label,
is_unix_fs(peer_sk) ? &peer_path : NULL));
} else if (!is_sk_fs) {
struct aa_sk_ctx *pctx = aa_sock(peer_sk);
unix_state_lock(peer_sk);
peer_path = unix_sk(peer_sk)->path;
if (peer_path.dentry)
path_get(&peer_path);
unix_state_unlock(peer_sk);
rcu_read_lock();
plabel = aa_get_newest_label(pctx->label);
rcu_read_unlock();
/* no fs check of aa_unix_peer_perm because conditions above
* ensure they will never be done
*/
last_error(error,
xcheck(unix_peer_perm(subj_cred, label, op,
last_error(error,
unix_fs_perm(op, request, subj_cred, label,
&peer_path));
if (peer_path.dentry)
path_put(&peer_path);
} else {
struct aa_sk_ctx *pctx = aa_sock(peer_sk);
rcu_read_lock();
plabel = aa_get_newest_label(pctx->label);
rcu_read_unlock();
/* no fs check of aa_unix_peer_perm because conditions
* above ensure they will never be done
*/
last_error(error,
xcheck(unix_peer_perm(subj_cred, label, op,
MAY_READ | MAY_WRITE, sock->sk,
is_sk_fs ? &path : NULL,
peer_addr, peer_addrlen,
is_unix_fs(peer_sk) ?
&peer_path : NULL,
plabel),
unix_peer_perm(file->f_cred, plabel, op,
NULL, plabel),
unix_peer_perm(file->f_cred, plabel, op,
MAY_READ | MAY_WRITE, peer_sk,
is_unix_fs(peer_sk) ?
&peer_path : NULL,
addr, addrlen,
NULL, addr, addrlen,
is_sk_fs ? &path : NULL,
label)));
if (!error && !__aa_subj_label_is_cached(plabel, label))
update_peer_ctx(peer_sk, pctx, label);
if (!error && !__aa_subj_label_is_cached(plabel, label))
update_peer_ctx(peer_sk, pctx, label);
}
}
sock_put(peer_sk);