Commit 5d5fe8bc authored by David Howells's avatar David Howells Committed by Jakub Kicinski
Browse files

rxrpc: Fix data-race warning and potential load/store tearing



Fix the following:

        BUG: KCSAN: data-race in rxrpc_peer_keepalive_worker / rxrpc_send_data_packet

which is reporting an issue with the reads and writes to ->last_tx_at in:

        conn->peer->last_tx_at = ktime_get_seconds();

and:

        keepalive_at = peer->last_tx_at + RXRPC_KEEPALIVE_TIME;

The lockless accesses to these to values aren't actually a problem as the
read only needs an approximate time of last transmission for the purposes
of deciding whether or not the transmission of a keepalive packet is
warranted yet.

Also, as ->last_tx_at is a 64-bit value, tearing can occur on a 32-bit
arch.

Fix both of these by switching to an unsigned int for ->last_tx_at and only
storing the LSW of the time64_t.  It can then be reconstructed at need
provided no more than 68 years has elapsed since the last transmission.

Fixes: ace45bec ("rxrpc: Fix firewall route keepalive")
Reported-by: default avatar <syzbot+6182afad5045e6703b3d@syzkaller.appspotmail.com>
Closes: https://lore.kernel.org/r/695e7cfb.050a0220.1c677c.036b.GAE@google.com/


Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: Simon Horman <horms@kernel.org>
cc: linux-afs@lists.infradead.org
cc: stable@kernel.org
Link: https://patch.msgid.link/1107124.1768903985@warthog.procyon.org.uk


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 869f3f7f
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -387,7 +387,7 @@ struct rxrpc_peer {
	struct rb_root		service_conns;	/* Service connections */
	struct list_head	keepalive_link;	/* Link in net->peer_keepalive[] */
	unsigned long		app_data;	/* Application data (e.g. afs_server) */
	time64_t		last_tx_at;	/* Last time packet sent here */
	unsigned int		last_tx_at;	/* Last time packet sent here (time64_t LSW) */
	seqlock_t		service_conn_lock;
	spinlock_t		lock;		/* access lock */
	int			debug_id;	/* debug ID for printks */
@@ -1379,6 +1379,13 @@ void rxrpc_peer_keepalive_worker(struct work_struct *);
void rxrpc_input_probe_for_pmtud(struct rxrpc_connection *conn, rxrpc_serial_t acked_serial,
				 bool sendmsg_fail);

/* Update the last transmission time on a peer for keepalive purposes. */
static inline void rxrpc_peer_mark_tx(struct rxrpc_peer *peer)
{
	/* To avoid tearing on 32-bit systems, we only keep the LSW. */
	WRITE_ONCE(peer->last_tx_at, ktime_get_seconds());
}

/*
 * peer_object.c
 */
+1 −1
Original line number Diff line number Diff line
@@ -194,7 +194,7 @@ void rxrpc_conn_retransmit_call(struct rxrpc_connection *conn,
	}

	ret = kernel_sendmsg(conn->local->socket, &msg, iov, ioc, len);
	conn->peer->last_tx_at = ktime_get_seconds();
	rxrpc_peer_mark_tx(conn->peer);
	if (ret < 0)
		trace_rxrpc_tx_fail(chan->call_debug_id, serial, ret,
				    rxrpc_tx_point_call_final_resend);
+7 −7
Original line number Diff line number Diff line
@@ -275,7 +275,7 @@ static void rxrpc_send_ack_packet(struct rxrpc_call *call, int nr_kv, size_t len
	rxrpc_local_dont_fragment(conn->local, why == rxrpc_propose_ack_ping_for_mtu_probe);

	ret = do_udp_sendmsg(conn->local->socket, &msg, len);
	call->peer->last_tx_at = ktime_get_seconds();
	rxrpc_peer_mark_tx(call->peer);
	if (ret < 0) {
		trace_rxrpc_tx_fail(call->debug_id, serial, ret,
				    rxrpc_tx_point_call_ack);
@@ -411,7 +411,7 @@ int rxrpc_send_abort_packet(struct rxrpc_call *call)

	iov_iter_kvec(&msg.msg_iter, WRITE, iov, 1, sizeof(pkt));
	ret = do_udp_sendmsg(conn->local->socket, &msg, sizeof(pkt));
	conn->peer->last_tx_at = ktime_get_seconds();
	rxrpc_peer_mark_tx(conn->peer);
	if (ret < 0)
		trace_rxrpc_tx_fail(call->debug_id, serial, ret,
				    rxrpc_tx_point_call_abort);
@@ -698,7 +698,7 @@ void rxrpc_send_data_packet(struct rxrpc_call *call, struct rxrpc_send_data_req
			ret = 0;
			trace_rxrpc_tx_data(call, txb->seq, txb->serial, txb->flags,
					    rxrpc_txdata_inject_loss);
			conn->peer->last_tx_at = ktime_get_seconds();
			rxrpc_peer_mark_tx(conn->peer);
			goto done;
		}
	}
@@ -711,7 +711,7 @@ void rxrpc_send_data_packet(struct rxrpc_call *call, struct rxrpc_send_data_req
	 */
	rxrpc_inc_stat(call->rxnet, stat_tx_data_send);
	ret = do_udp_sendmsg(conn->local->socket, &msg, len);
	conn->peer->last_tx_at = ktime_get_seconds();
	rxrpc_peer_mark_tx(conn->peer);

	if (ret == -EMSGSIZE) {
		rxrpc_inc_stat(call->rxnet, stat_tx_data_send_msgsize);
@@ -797,7 +797,7 @@ void rxrpc_send_conn_abort(struct rxrpc_connection *conn)

	trace_rxrpc_tx_packet(conn->debug_id, &whdr, rxrpc_tx_point_conn_abort);

	conn->peer->last_tx_at = ktime_get_seconds();
	rxrpc_peer_mark_tx(conn->peer);
}

/*
@@ -917,7 +917,7 @@ void rxrpc_send_keepalive(struct rxrpc_peer *peer)
		trace_rxrpc_tx_packet(peer->debug_id, &whdr,
				      rxrpc_tx_point_version_keepalive);

	peer->last_tx_at = ktime_get_seconds();
	rxrpc_peer_mark_tx(peer);
	_leave("");
}

@@ -973,7 +973,7 @@ void rxrpc_send_response(struct rxrpc_connection *conn, struct sk_buff *response
	if (ret < 0)
		goto fail;

	conn->peer->last_tx_at = ktime_get_seconds();
	rxrpc_peer_mark_tx(conn->peer);
	return;

fail:
+16 −1
Original line number Diff line number Diff line
@@ -237,6 +237,21 @@ static void rxrpc_distribute_error(struct rxrpc_peer *peer, struct sk_buff *skb,
	spin_unlock_irq(&peer->lock);
}

/*
 * Reconstruct the last transmission time.  The difference calculated should be
 * valid provided no more than ~68 years elapsed since the last transmission.
 */
static time64_t rxrpc_peer_get_tx_mark(const struct rxrpc_peer *peer, time64_t base)
{
	s32 last_tx_at = READ_ONCE(peer->last_tx_at);
	s32 base_lsw = base;
	s32 diff = last_tx_at - base_lsw;

	diff = clamp(diff, -RXRPC_KEEPALIVE_TIME, RXRPC_KEEPALIVE_TIME);

	return diff + base;
}

/*
 * Perform keep-alive pings.
 */
@@ -265,7 +280,7 @@ static void rxrpc_peer_keepalive_dispatch(struct rxrpc_net *rxnet,
		spin_unlock_bh(&rxnet->peer_hash_lock);

		if (use) {
			keepalive_at = peer->last_tx_at + RXRPC_KEEPALIVE_TIME;
			keepalive_at = rxrpc_peer_get_tx_mark(peer, base) + RXRPC_KEEPALIVE_TIME;
			slot = keepalive_at - base;
			_debug("%02x peer %u t=%d {%pISp}",
			       cursor, peer->debug_id, slot, &peer->srx.transport);
+2 −2
Original line number Diff line number Diff line
@@ -296,13 +296,13 @@ static int rxrpc_peer_seq_show(struct seq_file *seq, void *v)

	now = ktime_get_seconds();
	seq_printf(seq,
		   "UDP   %-47.47s %-47.47s %3u %4u %5u %6llus %8d %8d\n",
		   "UDP   %-47.47s %-47.47s %3u %4u %5u %6ds %8d %8d\n",
		   lbuff,
		   rbuff,
		   refcount_read(&peer->ref),
		   peer->cong_ssthresh,
		   peer->max_data,
		   now - peer->last_tx_at,
		   (s32)now - (s32)READ_ONCE(peer->last_tx_at),
		   READ_ONCE(peer->recent_srtt_us),
		   READ_ONCE(peer->recent_rto_us));

Loading