NFSv4: Fix CLOSE races with OPEN
authorTrond Myklebust <trond.myklebust@primarydata.com>
Mon, 14 Nov 2016 16:19:55 +0000 (11:19 -0500)
committerAnna Schumaker <Anna.Schumaker@Netapp.com>
Fri, 18 Nov 2016 18:35:58 +0000 (13:35 -0500)
If the reply to a successful CLOSE call races with an OPEN to the same
file, we can end up scribbling over the stateid that represents the
new open state.
The race looks like:

  Client Server
  ====== ======

  CLOSE stateid A on file "foo"
CLOSE stateid A, return stateid C
  OPEN file "foo"
OPEN "foo", return stateid B
  Receive reply to OPEN
  Reset open state for "foo"
  Associate stateid B to "foo"

  Receive CLOSE for A
  Reset open state for "foo"
  Replace stateid B with C

The fix is to examine the argument of the CLOSE, and check for a match
with the current stateid "other" field. If the two do not match, then
the above race occurred, and we should just ignore the CLOSE.

Reported-by: Benjamin Coddington <bcodding@redhat.com>
Signed-off-by: Trond Myklebust <trond.myklebust@primarydata.com>
Signed-off-by: Anna Schumaker <Anna.Schumaker@Netapp.com>
fs/nfs/nfs4_fs.h
fs/nfs/nfs4proc.c

index 9b3a82abab079f0a03047260ed2ea4e3ee9154ed..1452177c822dbc4a1be8d7e164de88d636764aed 100644 (file)
@@ -542,6 +542,13 @@ static inline bool nfs4_valid_open_stateid(const struct nfs4_state *state)
        return test_bit(NFS_STATE_RECOVERY_FAILED, &state->flags) == 0;
 }
 
+static inline bool nfs4_state_match_open_stateid_other(const struct nfs4_state *state,
+               const nfs4_stateid *stateid)
+{
+       return test_bit(NFS_OPEN_STATE, &state->flags) &&
+               nfs4_stateid_match_other(&state->open_stateid, stateid);
+}
+
 #else
 
 #define nfs4_close_state(a, b) do { } while (0)
index 8e25327077e24a109812c29babfc64ec0c550e96..0b3cdf8563339bf194a38221d6f9c17f3f12fee2 100644 (file)
@@ -1451,7 +1451,6 @@ static void nfs_resync_open_stateid_locked(struct nfs4_state *state)
 }
 
 static void nfs_clear_open_stateid_locked(struct nfs4_state *state,
-               nfs4_stateid *arg_stateid,
                nfs4_stateid *stateid, fmode_t fmode)
 {
        clear_bit(NFS_O_RDWR_STATE, &state->flags);
@@ -1469,10 +1468,9 @@ static void nfs_clear_open_stateid_locked(struct nfs4_state *state,
        }
        if (stateid == NULL)
                return;
-       /* Handle races with OPEN */
-       if (!nfs4_stateid_match_other(arg_stateid, &state->open_stateid) ||
-           (nfs4_stateid_match_other(stateid, &state->open_stateid) &&
-           !nfs4_stateid_is_newer(stateid, &state->open_stateid))) {
+       /* Handle OPEN+OPEN_DOWNGRADE races */
+       if (nfs4_stateid_match_other(stateid, &state->open_stateid) &&
+           !nfs4_stateid_is_newer(stateid, &state->open_stateid)) {
                nfs_resync_open_stateid_locked(state);
                return;
        }
@@ -1486,7 +1484,9 @@ static void nfs_clear_open_stateid(struct nfs4_state *state,
        nfs4_stateid *stateid, fmode_t fmode)
 {
        write_seqlock(&state->seqlock);
-       nfs_clear_open_stateid_locked(state, arg_stateid, stateid, fmode);
+       /* Ignore, if the CLOSE argment doesn't match the current stateid */
+       if (nfs4_state_match_open_stateid_other(state, arg_stateid))
+               nfs_clear_open_stateid_locked(state, stateid, fmode);
        write_sequnlock(&state->seqlock);
        if (test_bit(NFS_STATE_RECLAIM_NOGRACE, &state->flags))
                nfs4_schedule_state_manager(state->owner->so_server->nfs_client);