diff options
author | Marcel Telka <marcel.telka@nexenta.com> | 2014-12-15 17:38:51 +0100 |
---|---|---|
committer | Dan McDonald <danmcd@omniti.com> | 2014-12-16 11:11:15 -0500 |
commit | 3d1d816f0f1e405a11a5871fb93eef11577c5fc8 (patch) | |
tree | 193dc59434e4d6acdbc9c0de2602c23c7e5c9570 /usr/src | |
parent | a74909fd1b8a12f99797bbadb7a8af8bdca8596c (diff) | |
download | illumos-gate-3d1d816f0f1e405a11a5871fb93eef11577c5fc8.tar.gz |
5436 Panic due to bad mutex, from auth_cache being previously freed
Reviewed by: Garrett D'Amore <garrett@damore.org>
Approved by: Dan McDonald <danmcd@omniti.com>
Diffstat (limited to 'usr/src')
-rw-r--r-- | usr/src/uts/common/fs/nfs/nfs_auth.c | 69 |
1 files changed, 46 insertions, 23 deletions
diff --git a/usr/src/uts/common/fs/nfs/nfs_auth.c b/usr/src/uts/common/fs/nfs/nfs_auth.c index 18d358795b..d18c6767fc 100644 --- a/usr/src/uts/common/fs/nfs/nfs_auth.c +++ b/usr/src/uts/common/fs/nfs/nfs_auth.c @@ -783,7 +783,7 @@ nfsauth_cache_get(struct exportinfo *exi, struct svc_req *req, int flavor, struct netbuf *claddr; struct auth_cache **head; struct auth_cache *p; - struct auth_cache *prev = NULL; + struct auth_cache *prev; int access; time_t refresh; @@ -818,7 +818,9 @@ nfsauth_cache_get(struct exportinfo *exi, struct svc_req *req, int flavor, rw_enter(&exi->exi_cache_lock, RW_READER); head = &exi->exi_cache[hash(&addr)]; - for (p = *head; p; p = p->auth_next) { +retry: + prev = NULL; + for (p = *head; p != NULL; p = p->auth_next) { if (EQADDR(&addr, &p->auth_addr) && flavor == p->auth_flavor && crgetuid(cr) == p->auth_clnt_uid && crgetgid(cr) == p->auth_clnt_gid) @@ -831,44 +833,65 @@ nfsauth_cache_get(struct exportinfo *exi, struct svc_req *req, int flavor, * In a case the client's supplemental groups changed we need * to discard the auth_cache entry and re-retrieve it. */ - mutex_enter(&p->auth_lock); if (p->auth_clnt_ngids != crgetngroups(cr) || bcmp(p->auth_clnt_gids, crgetgroups(cr), p->auth_clnt_ngids * sizeof (gid_t))) { - auth_state_t prev_state = p->auth_state; + struct auth_cache *next; - p->auth_state = NFS_AUTH_INVALID; - mutex_exit(&p->auth_lock); - - if (prev_state == NFS_AUTH_FRESH) { + /* + * To remove the auth_cache entry from exi we need to + * hold the exi_cache_lock for write. If we do not + * have it yet, we will try to upgrade, or re-lock as a + * last resort. In a case of re-lock we will retry the + * search for the auth_cache entry because it might + * changed in the meantime. + */ + ASSERT(RW_LOCK_HELD(&exi->exi_cache_lock)); + if (rw_read_locked(&exi->exi_cache_lock) != 0) { if (rw_tryupgrade(&exi->exi_cache_lock) == 0) { - struct auth_cache *tmp; - rw_exit(&exi->exi_cache_lock); rw_enter(&exi->exi_cache_lock, RW_WRITER); - prev = NULL; - for (tmp = *head; tmp != NULL; - tmp = tmp->auth_next) { - if (p == tmp) - break; - prev = p; - } + goto retry; } + } - if (prev == NULL) - exi->exi_cache[hash(&addr)] = - p->auth_next; - else - prev->auth_next = p->auth_next; + /* + * Now, remove the entry from exi and free it, or place + * it at the dead list. + */ + next = p->auth_next; + mutex_enter(&p->auth_lock); + if (p->auth_state != NFS_AUTH_FRESH) { + p->auth_state = NFS_AUTH_INVALID; + mutex_exit(&p->auth_lock); + mutex_enter(&refreshq_lock); + p->auth_next = refreshq_dead_entries; + refreshq_dead_entries = p; + mutex_exit(&refreshq_lock); + } else { + mutex_exit(&p->auth_lock); nfsauth_free_node(p); } + /* + * Finally, disconnect the entry from exi + */ + if (prev == NULL) + *head = next; + else + prev->auth_next = next; + goto retrieve; } - mutex_exit(&p->auth_lock); + + /* + * If we hold the lock for write, downgrade + */ + if (rw_read_locked(&exi->exi_cache_lock) == 0) + rw_downgrade(&exi->exi_cache_lock); nfsauth_cache_hit++; |