]> git.proxmox.com Git - mirror_ubuntu-focal-kernel.git/commitdiff
afs: Protect call->state changes against signals
authorDavid Howells <dhowells@redhat.com>
Thu, 2 Nov 2017 15:27:53 +0000 (15:27 +0000)
committerDavid Howells <dhowells@redhat.com>
Mon, 13 Nov 2017 15:38:21 +0000 (15:38 +0000)
Protect call->state changes against the call being prematurely terminated
due to a signal.

What can happen is that a signal causes afs_wait_for_call_to_complete() to
abort an afs_call because it's not yet complete whilst afs_deliver_to_call()
is delivering data to that call.

If the data delivery causes the state to change, this may overwrite the state
of the afs_call, making it not-yet-complete again - but no further
notifications will be forthcoming from AF_RXRPC as the rxrpc call has been
aborted and completed, so kAFS will just hang in various places waiting for
that call or on page bits that need clearing by that call.

A tracepoint to monitor call state changes is also provided.

Signed-off-by: David Howells <dhowells@redhat.com>
fs/afs/cmservice.c
fs/afs/internal.h
fs/afs/rxrpc.c
include/trace/events/afs.h

index 5767f540e0e114e6bab903ab1b2828b47492fa2c..41e277f57b20f5e03833abc42aaacea75aff2f73 100644 (file)
@@ -188,7 +188,6 @@ static int afs_deliver_cb_callback(struct afs_call *call)
 
        switch (call->unmarshall) {
        case 0:
-               rxrpc_kernel_get_peer(call->net->socket, call->rxcall, &srx);
                call->offset = 0;
                call->unmarshall++;
 
@@ -281,10 +280,12 @@ static int afs_deliver_cb_callback(struct afs_call *call)
                break;
        }
 
-       call->state = AFS_CALL_REPLYING;
+       if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+               return -EIO;
 
        /* we'll need the file server record as that tells us which set of
         * vnodes to operate upon */
+       rxrpc_kernel_get_peer(call->net->socket, call->rxcall, &srx);
        server = afs_find_server(call->net, &srx);
        if (!server)
                return -ENOTCONN;
@@ -325,9 +326,6 @@ static int afs_deliver_cb_init_call_back_state(struct afs_call *call)
        if (ret < 0)
                return ret;
 
-       /* no unmarshalling required */
-       call->state = AFS_CALL_REPLYING;
-
        /* we'll need the file server record as that tells us which set of
         * vnodes to operate upon */
        server = afs_find_server(call->net, &srx);
@@ -352,8 +350,6 @@ static int afs_deliver_cb_init_call_back_state3(struct afs_call *call)
 
        _enter("");
 
-       rxrpc_kernel_get_peer(call->net->socket, call->rxcall, &srx);
-
        _enter("{%u}", call->unmarshall);
 
        switch (call->unmarshall) {
@@ -397,11 +393,12 @@ static int afs_deliver_cb_init_call_back_state3(struct afs_call *call)
                break;
        }
 
-       /* no unmarshalling required */
-       call->state = AFS_CALL_REPLYING;
+       if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+               return -EIO;
 
        /* we'll need the file server record as that tells us which set of
         * vnodes to operate upon */
+       rxrpc_kernel_get_peer(call->net->socket, call->rxcall, &srx);
        server = afs_find_server(call->net, &srx);
        if (!server)
                return -ENOTCONN;
@@ -436,8 +433,8 @@ static int afs_deliver_cb_probe(struct afs_call *call)
        if (ret < 0)
                return ret;
 
-       /* no unmarshalling required */
-       call->state = AFS_CALL_REPLYING;
+       if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+               return -EIO;
 
        return afs_queue_call_work(call);
 }
@@ -519,7 +516,8 @@ static int afs_deliver_cb_probe_uuid(struct afs_call *call)
                break;
        }
 
-       call->state = AFS_CALL_REPLYING;
+       if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+               return -EIO;
 
        return afs_queue_call_work(call);
 }
@@ -600,8 +598,8 @@ static int afs_deliver_cb_tell_me_about_yourself(struct afs_call *call)
        if (ret < 0)
                return ret;
 
-       /* no unmarshalling required */
-       call->state = AFS_CALL_REPLYING;
+       if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+               return -EIO;
 
        return afs_queue_call_work(call);
 }
index 1de36e6abd5ed6255794bc4055e9a28747a6679d..bd8dcee7e066719439d5decb0d970e6142d167eb 100644 (file)
@@ -51,13 +51,14 @@ struct afs_iget_data {
 };
 
 enum afs_call_state {
-       AFS_CALL_REQUESTING,    /* request is being sent for outgoing call */
-       AFS_CALL_AWAIT_REPLY,   /* awaiting reply to outgoing call */
-       AFS_CALL_AWAIT_OP_ID,   /* awaiting op ID on incoming call */
-       AFS_CALL_AWAIT_REQUEST, /* awaiting request data on incoming call */
-       AFS_CALL_REPLYING,      /* replying to incoming call */
-       AFS_CALL_AWAIT_ACK,     /* awaiting final ACK of incoming call */
-       AFS_CALL_COMPLETE,      /* Completed or failed */
+       AFS_CALL_CL_REQUESTING,         /* Client: Request is being sent */
+       AFS_CALL_CL_AWAIT_REPLY,        /* Client: Awaiting reply */
+       AFS_CALL_CL_PROC_REPLY,         /* Client: rxrpc call complete; processing reply */
+       AFS_CALL_SV_AWAIT_OP_ID,        /* Server: Awaiting op ID */
+       AFS_CALL_SV_AWAIT_REQUEST,      /* Server: Awaiting request data */
+       AFS_CALL_SV_REPLYING,           /* Server: Replying */
+       AFS_CALL_SV_AWAIT_ACK,          /* Server: Awaiting final ACK */
+       AFS_CALL_COMPLETE,              /* Completed or failed */
 };
 
 /*
@@ -97,6 +98,7 @@ struct afs_call {
        size_t                  offset;         /* offset into received data store */
        atomic_t                usage;
        enum afs_call_state     state;
+       spinlock_t              state_lock;
        int                     error;          /* error code */
        u32                     abort_code;     /* Remote abort ID or 0 */
        unsigned                request_size;   /* size of request data */
@@ -543,6 +545,8 @@ struct afs_fs_cursor {
 #define AFS_FS_CURSOR_NO_VSLEEP        0x0020          /* Set to prevent sleep on VBUSY, VOFFLINE, ... */
 };
 
+#include <trace/events/afs.h>
+
 /*****************************************************************************/
 /*
  * addr_list.c
@@ -788,6 +792,49 @@ static inline int afs_transfer_reply(struct afs_call *call)
        return afs_extract_data(call, call->buffer, call->reply_max, false);
 }
 
+static inline bool afs_check_call_state(struct afs_call *call,
+                                       enum afs_call_state state)
+{
+       return READ_ONCE(call->state) == state;
+}
+
+static inline bool afs_set_call_state(struct afs_call *call,
+                                     enum afs_call_state from,
+                                     enum afs_call_state to)
+{
+       bool ok = false;
+
+       spin_lock_bh(&call->state_lock);
+       if (call->state == from) {
+               call->state = to;
+               trace_afs_call_state(call, from, to, 0, 0);
+               ok = true;
+       }
+       spin_unlock_bh(&call->state_lock);
+       return ok;
+}
+
+static inline void afs_set_call_complete(struct afs_call *call,
+                                        int error, u32 remote_abort)
+{
+       enum afs_call_state state;
+       bool ok = false;
+
+       spin_lock_bh(&call->state_lock);
+       state = call->state;
+       if (state != AFS_CALL_COMPLETE) {
+               call->abort_code = remote_abort;
+               call->error = error;
+               call->state = AFS_CALL_COMPLETE;
+               trace_afs_call_state(call, state, AFS_CALL_COMPLETE,
+                                    error, remote_abort);
+               ok = true;
+       }
+       spin_unlock_bh(&call->state_lock);
+       if (ok)
+               trace_afs_call_done(call);
+}
+
 /*
  * security.c
  */
@@ -932,8 +979,6 @@ static inline void afs_check_for_remote_deletion(struct afs_fs_cursor *fc,
 /*
  * debug tracing
  */
-#include <trace/events/afs.h>
-
 extern unsigned afs_debug;
 
 #define dbgprintk(FMT,...) \
index bd44ae8b63d856949b91615ff785ba671d2283f4..ea1460b9b71aab5900b342e28641924a0f7b9f04 100644 (file)
@@ -134,6 +134,7 @@ static struct afs_call *afs_alloc_call(struct afs_net *net,
        atomic_set(&call->usage, 1);
        INIT_WORK(&call->async_work, afs_process_async_call);
        init_waitqueue_head(&call->waitq);
+       spin_lock_init(&call->state_lock);
 
        o = atomic_inc_return(&net->nr_outstanding_calls);
        trace_afs_call(call, afs_call_trace_alloc, 1, o,
@@ -288,8 +289,7 @@ static void afs_notify_end_request_tx(struct sock *sock,
 {
        struct afs_call *call = (struct afs_call *)call_user_ID;
 
-       if (call->state == AFS_CALL_REQUESTING)
-               call->state = AFS_CALL_AWAIT_REPLY;
+       afs_set_call_state(call, AFS_CALL_CL_REQUESTING, AFS_CALL_CL_AWAIT_REPLY);
 }
 
 /*
@@ -444,82 +444,87 @@ error_kill_call:
  */
 static void afs_deliver_to_call(struct afs_call *call)
 {
-       u32 abort_code;
+       enum afs_call_state state;
+       u32 abort_code, remote_abort = 0;
        int ret;
 
        _enter("%s", call->type->name);
 
-       while (call->state == AFS_CALL_AWAIT_REPLY ||
-              call->state == AFS_CALL_AWAIT_OP_ID ||
-              call->state == AFS_CALL_AWAIT_REQUEST ||
-              call->state == AFS_CALL_AWAIT_ACK
+       while (state = READ_ONCE(call->state),
+              state == AFS_CALL_CL_AWAIT_REPLY ||
+              state == AFS_CALL_SV_AWAIT_OP_ID ||
+              state == AFS_CALL_SV_AWAIT_REQUEST ||
+              state == AFS_CALL_SV_AWAIT_ACK
               ) {
-               if (call->state == AFS_CALL_AWAIT_ACK) {
+               if (state == AFS_CALL_SV_AWAIT_ACK) {
                        size_t offset = 0;
                        ret = rxrpc_kernel_recv_data(call->net->socket,
                                                     call->rxcall,
                                                     NULL, 0, &offset, false,
-                                                    &call->abort_code,
+                                                    &remote_abort,
                                                     &call->service_id);
                        trace_afs_recv_data(call, 0, offset, false, ret);
 
                        if (ret == -EINPROGRESS || ret == -EAGAIN)
                                return;
-                       if (ret < 0)
-                               call->error = ret;
-                       if (ret < 0 || ret == 1)
+                       if (ret < 0 || ret == 1) {
+                               if (ret == 1)
+                                       ret = 0;
                                goto call_complete;
+                       }
                        return;
                }
 
                ret = call->type->deliver(call);
+               state = READ_ONCE(call->state);
                switch (ret) {
                case 0:
-                       if (call->state == AFS_CALL_AWAIT_REPLY)
+                       if (state == AFS_CALL_CL_PROC_REPLY)
                                goto call_complete;
+                       ASSERTCMP(state, >, AFS_CALL_CL_PROC_REPLY);
                        goto done;
                case -EINPROGRESS:
                case -EAGAIN:
                        goto out;
+               case -EIO:
                case -ECONNABORTED:
-                       goto save_error;
+                       ASSERTCMP(state, ==, AFS_CALL_COMPLETE);
+                       goto done;
                case -ENOTCONN:
                        abort_code = RX_CALL_DEAD;
                        rxrpc_kernel_abort_call(call->net->socket, call->rxcall,
                                                abort_code, ret, "KNC");
-                       goto save_error;
+                       goto local_abort;
                case -ENOTSUPP:
                        abort_code = RXGEN_OPCODE;
                        rxrpc_kernel_abort_call(call->net->socket, call->rxcall,
                                                abort_code, ret, "KIV");
-                       goto save_error;
+                       goto local_abort;
                case -ENODATA:
                case -EBADMSG:
                case -EMSGSIZE:
                default:
                        abort_code = RXGEN_CC_UNMARSHAL;
-                       if (call->state != AFS_CALL_AWAIT_REPLY)
+                       if (state != AFS_CALL_CL_AWAIT_REPLY)
                                abort_code = RXGEN_SS_UNMARSHAL;
                        rxrpc_kernel_abort_call(call->net->socket, call->rxcall,
                                                abort_code, -EBADMSG, "KUM");
-                       goto save_error;
+                       goto local_abort;
                }
        }
 
 done:
-       if (call->state == AFS_CALL_COMPLETE && call->incoming)
+       if (state == AFS_CALL_COMPLETE && call->incoming)
                afs_put_call(call);
 out:
        _leave("");
        return;
 
-save_error:
-       call->error = ret;
+local_abort:
+       abort_code = 0;
 call_complete:
-       if (call->state != AFS_CALL_COMPLETE) {
-               call->state = AFS_CALL_COMPLETE;
-               trace_afs_call_done(call);
-       }
+       afs_set_call_complete(call, ret, remote_abort);
+       state = AFS_CALL_COMPLETE;
        goto done;
 }
 
@@ -551,14 +556,15 @@ static long afs_wait_for_call_to_complete(struct afs_call *call,
                set_current_state(TASK_UNINTERRUPTIBLE);
 
                /* deliver any messages that are in the queue */
-               if (call->state < AFS_CALL_COMPLETE && call->need_attention) {
+               if (!afs_check_call_state(call, AFS_CALL_COMPLETE) &&
+                   call->need_attention) {
                        call->need_attention = false;
                        __set_current_state(TASK_RUNNING);
                        afs_deliver_to_call(call);
                        continue;
                }
 
-               if (call->state == AFS_CALL_COMPLETE)
+               if (afs_check_call_state(call, AFS_CALL_COMPLETE))
                        break;
 
                life = rxrpc_kernel_check_life(call->net->socket, call->rxcall);
@@ -578,17 +584,17 @@ static long afs_wait_for_call_to_complete(struct afs_call *call,
        __set_current_state(TASK_RUNNING);
 
        /* Kill off the call if it's still live. */
-       if (call->state < AFS_CALL_COMPLETE) {
+       if (!afs_check_call_state(call, AFS_CALL_COMPLETE)) {
                _debug("call interrupted");
                if (rxrpc_kernel_abort_call(call->net->socket, call->rxcall,
-                                           RX_USER_ABORT, -EINTR, "KWI")) {
-                       call->error = -ERESTARTSYS;
-                       trace_afs_call_done(call);
-               }
+                                           RX_USER_ABORT, -EINTR, "KWI"))
+                       afs_set_call_complete(call, -EINTR, 0);
        }
 
+       spin_lock_bh(&call->state_lock);
        ac->abort_code = call->abort_code;
        ac->error = call->error;
+       spin_unlock_bh(&call->state_lock);
 
        ret = ac->error;
        switch (ret) {
@@ -713,7 +719,7 @@ void afs_charge_preallocation(struct work_struct *work)
                                break;
 
                        call->async = true;
-                       call->state = AFS_CALL_AWAIT_OP_ID;
+                       call->state = AFS_CALL_SV_AWAIT_OP_ID;
                        init_waitqueue_head(&call->waitq);
                }
 
@@ -769,7 +775,7 @@ static int afs_deliver_cm_op_id(struct afs_call *call)
                return ret;
 
        call->operation_ID = ntohl(call->tmp);
-       call->state = AFS_CALL_AWAIT_REQUEST;
+       afs_set_call_state(call, AFS_CALL_SV_AWAIT_OP_ID, AFS_CALL_SV_AWAIT_REQUEST);
        call->offset = 0;
 
        /* ask the cache manager to route the call (it'll change the call type
@@ -794,8 +800,7 @@ static void afs_notify_end_reply_tx(struct sock *sock,
 {
        struct afs_call *call = (struct afs_call *)call_user_ID;
 
-       if (call->state == AFS_CALL_REPLYING)
-               call->state = AFS_CALL_AWAIT_ACK;
+       afs_set_call_state(call, AFS_CALL_SV_REPLYING, AFS_CALL_SV_AWAIT_ACK);
 }
 
 /*
@@ -879,6 +884,8 @@ int afs_extract_data(struct afs_call *call, void *buf, size_t count,
                     bool want_more)
 {
        struct afs_net *net = call->net;
+       enum afs_call_state state;
+       u32 remote_abort;
        int ret;
 
        _enter("{%s,%zu},,%zu,%d",
@@ -888,29 +895,30 @@ int afs_extract_data(struct afs_call *call, void *buf, size_t count,
 
        ret = rxrpc_kernel_recv_data(net->socket, call->rxcall,
                                     buf, count, &call->offset,
-                                    want_more, &call->abort_code,
+                                    want_more, &remote_abort,
                                     &call->service_id);
        trace_afs_recv_data(call, count, call->offset, want_more, ret);
        if (ret == 0 || ret == -EAGAIN)
                return ret;
 
+       state = READ_ONCE(call->state);
        if (ret == 1) {
-               switch (call->state) {
-               case AFS_CALL_AWAIT_REPLY:
-                       call->state = AFS_CALL_COMPLETE;
-                       trace_afs_call_done(call);
+               switch (state) {
+               case AFS_CALL_CL_AWAIT_REPLY:
+                       afs_set_call_state(call, state, AFS_CALL_CL_PROC_REPLY);
                        break;
-               case AFS_CALL_AWAIT_REQUEST:
-                       call->state = AFS_CALL_REPLYING;
+               case AFS_CALL_SV_AWAIT_REQUEST:
+                       afs_set_call_state(call, state, AFS_CALL_SV_REPLYING);
                        break;
+               case AFS_CALL_COMPLETE:
+                       kdebug("prem complete %d", call->error);
+                       return -EIO;
                default:
                        break;
                }
                return 0;
        }
 
-       call->error = ret;
-       call->state = AFS_CALL_COMPLETE;
-       trace_afs_call_done(call);
+       afs_set_call_complete(call, ret, remote_abort);
        return ret;
 }
index 5f4e8193932dd6968f1f8dd8fe970370fa927fd2..6b59c63a8e51451a7db183ec32c30624e52a20fe 100644 (file)
@@ -441,6 +441,36 @@ TRACE_EVENT(afs_page_dirty,
                      __entry->priv >> AFS_PRIV_SHIFT)
            );
 
+TRACE_EVENT(afs_call_state,
+           TP_PROTO(struct afs_call *call,
+                    enum afs_call_state from,
+                    enum afs_call_state to,
+                    int ret, u32 remote_abort),
+
+           TP_ARGS(call, from, to, ret, remote_abort),
+
+           TP_STRUCT__entry(
+                   __field(struct afs_call *,          call            )
+                   __field(enum afs_call_state,        from            )
+                   __field(enum afs_call_state,        to              )
+                   __field(int,                        ret             )
+                   __field(u32,                        abort           )
+                            ),
+
+           TP_fast_assign(
+                   __entry->call = call;
+                   __entry->from = from;
+                   __entry->to = to;
+                   __entry->ret = ret;
+                   __entry->abort = remote_abort;
+                          ),
+
+           TP_printk("c=%p %u->%u r=%d ab=%d",
+                     __entry->call,
+                     __entry->from, __entry->to,
+                     __entry->ret, __entry->abort)
+           );
+
 #endif /* _TRACE_AFS_H */
 
 /* This part must be outside protection */