1c167b9c7SMaximilian Luz // SPDX-License-Identifier: GPL-2.0+
2c167b9c7SMaximilian Luz /*
3c167b9c7SMaximilian Luz  * SSH message parser.
4c167b9c7SMaximilian Luz  *
5*221756e6SMaximilian Luz  * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
6c167b9c7SMaximilian Luz  */
7c167b9c7SMaximilian Luz 
8c167b9c7SMaximilian Luz #include <asm/unaligned.h>
9c167b9c7SMaximilian Luz #include <linux/compiler.h>
10c167b9c7SMaximilian Luz #include <linux/device.h>
11c167b9c7SMaximilian Luz #include <linux/types.h>
12c167b9c7SMaximilian Luz 
13c167b9c7SMaximilian Luz #include <linux/surface_aggregator/serial_hub.h>
14c167b9c7SMaximilian Luz #include "ssh_parser.h"
15c167b9c7SMaximilian Luz 
16c167b9c7SMaximilian Luz /**
17c167b9c7SMaximilian Luz  * sshp_validate_crc() - Validate a CRC in raw message data.
18c167b9c7SMaximilian Luz  * @src: The span of data over which the CRC should be computed.
19c167b9c7SMaximilian Luz  * @crc: The pointer to the expected u16 CRC value.
20c167b9c7SMaximilian Luz  *
21c167b9c7SMaximilian Luz  * Computes the CRC of the provided data span (@src), compares it to the CRC
22c167b9c7SMaximilian Luz  * stored at the given address (@crc), and returns the result of this
23c167b9c7SMaximilian Luz  * comparison, i.e. %true if equal. This function is intended to run on raw
24c167b9c7SMaximilian Luz  * input/message data.
25c167b9c7SMaximilian Luz  *
26c167b9c7SMaximilian Luz  * Return: Returns %true if the computed CRC matches the stored CRC, %false
27c167b9c7SMaximilian Luz  * otherwise.
28c167b9c7SMaximilian Luz  */
sshp_validate_crc(const struct ssam_span * src,const u8 * crc)29c167b9c7SMaximilian Luz static bool sshp_validate_crc(const struct ssam_span *src, const u8 *crc)
30c167b9c7SMaximilian Luz {
31c167b9c7SMaximilian Luz 	u16 actual = ssh_crc(src->ptr, src->len);
32c167b9c7SMaximilian Luz 	u16 expected = get_unaligned_le16(crc);
33c167b9c7SMaximilian Luz 
34c167b9c7SMaximilian Luz 	return actual == expected;
35c167b9c7SMaximilian Luz }
36c167b9c7SMaximilian Luz 
37c167b9c7SMaximilian Luz /**
38c167b9c7SMaximilian Luz  * sshp_starts_with_syn() - Check if the given data starts with SSH SYN bytes.
39c167b9c7SMaximilian Luz  * @src: The data span to check the start of.
40c167b9c7SMaximilian Luz  */
sshp_starts_with_syn(const struct ssam_span * src)41c167b9c7SMaximilian Luz static bool sshp_starts_with_syn(const struct ssam_span *src)
42c167b9c7SMaximilian Luz {
43c167b9c7SMaximilian Luz 	return src->len >= 2 && get_unaligned_le16(src->ptr) == SSH_MSG_SYN;
44c167b9c7SMaximilian Luz }
45c167b9c7SMaximilian Luz 
46c167b9c7SMaximilian Luz /**
47c167b9c7SMaximilian Luz  * sshp_find_syn() - Find SSH SYN bytes in the given data span.
48c167b9c7SMaximilian Luz  * @src: The data span to search in.
49c167b9c7SMaximilian Luz  * @rem: The span (output) indicating the remaining data, starting with SSH
50c167b9c7SMaximilian Luz  *       SYN bytes, if found.
51c167b9c7SMaximilian Luz  *
52c167b9c7SMaximilian Luz  * Search for SSH SYN bytes in the given source span. If found, set the @rem
53c167b9c7SMaximilian Luz  * span to the remaining data, starting with the first SYN bytes and capped by
54c167b9c7SMaximilian Luz  * the source span length, and return %true. This function does not copy any
55c167b9c7SMaximilian Luz  * data, but rather only sets pointers to the respective start addresses and
56c167b9c7SMaximilian Luz  * length values.
57c167b9c7SMaximilian Luz  *
58c167b9c7SMaximilian Luz  * If no SSH SYN bytes could be found, set the @rem span to the zero-length
59c167b9c7SMaximilian Luz  * span at the end of the source span and return %false.
60c167b9c7SMaximilian Luz  *
61c167b9c7SMaximilian Luz  * If partial SSH SYN bytes could be found at the end of the source span, set
62c167b9c7SMaximilian Luz  * the @rem span to cover these partial SYN bytes, capped by the end of the
63c167b9c7SMaximilian Luz  * source span, and return %false. This function should then be re-run once
64c167b9c7SMaximilian Luz  * more data is available.
65c167b9c7SMaximilian Luz  *
66c167b9c7SMaximilian Luz  * Return: Returns %true if a complete SSH SYN sequence could be found,
67c167b9c7SMaximilian Luz  * %false otherwise.
68c167b9c7SMaximilian Luz  */
sshp_find_syn(const struct ssam_span * src,struct ssam_span * rem)69c167b9c7SMaximilian Luz bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem)
70c167b9c7SMaximilian Luz {
71c167b9c7SMaximilian Luz 	size_t i;
72c167b9c7SMaximilian Luz 
73c167b9c7SMaximilian Luz 	for (i = 0; i < src->len - 1; i++) {
74c167b9c7SMaximilian Luz 		if (likely(get_unaligned_le16(src->ptr + i) == SSH_MSG_SYN)) {
75c167b9c7SMaximilian Luz 			rem->ptr = src->ptr + i;
76c167b9c7SMaximilian Luz 			rem->len = src->len - i;
77c167b9c7SMaximilian Luz 			return true;
78c167b9c7SMaximilian Luz 		}
79c167b9c7SMaximilian Luz 	}
80c167b9c7SMaximilian Luz 
81c167b9c7SMaximilian Luz 	if (unlikely(src->ptr[src->len - 1] == (SSH_MSG_SYN & 0xff))) {
82c167b9c7SMaximilian Luz 		rem->ptr = src->ptr + src->len - 1;
83c167b9c7SMaximilian Luz 		rem->len = 1;
84c167b9c7SMaximilian Luz 		return false;
85c167b9c7SMaximilian Luz 	}
86c167b9c7SMaximilian Luz 
87c167b9c7SMaximilian Luz 	rem->ptr = src->ptr + src->len;
88c167b9c7SMaximilian Luz 	rem->len = 0;
89c167b9c7SMaximilian Luz 	return false;
90c167b9c7SMaximilian Luz }
91c167b9c7SMaximilian Luz 
92c167b9c7SMaximilian Luz /**
93c167b9c7SMaximilian Luz  * sshp_parse_frame() - Parse SSH frame.
94c167b9c7SMaximilian Luz  * @dev: The device used for logging.
95c167b9c7SMaximilian Luz  * @source: The source to parse from.
96c167b9c7SMaximilian Luz  * @frame: The parsed frame (output).
97c167b9c7SMaximilian Luz  * @payload: The parsed payload (output).
98c167b9c7SMaximilian Luz  * @maxlen: The maximum supported message length.
99c167b9c7SMaximilian Luz  *
100c167b9c7SMaximilian Luz  * Parses and validates a SSH frame, including its payload, from the given
101c167b9c7SMaximilian Luz  * source. Sets the provided @frame pointer to the start of the frame and
102c167b9c7SMaximilian Luz  * writes the limits of the frame payload to the provided @payload span
103c167b9c7SMaximilian Luz  * pointer.
104c167b9c7SMaximilian Luz  *
105c167b9c7SMaximilian Luz  * This function does not copy any data, but rather only validates the message
106c167b9c7SMaximilian Luz  * data and sets pointers (and length values) to indicate the respective parts.
107c167b9c7SMaximilian Luz  *
108c167b9c7SMaximilian Luz  * If no complete SSH frame could be found, the frame pointer will be set to
109c167b9c7SMaximilian Luz  * the %NULL pointer and the payload span will be set to the null span (start
110c167b9c7SMaximilian Luz  * pointer %NULL, size zero).
111c167b9c7SMaximilian Luz  *
112c167b9c7SMaximilian Luz  * Return: Returns zero on success or if the frame is incomplete, %-ENOMSG if
113c167b9c7SMaximilian Luz  * the start of the message is invalid, %-EBADMSG if any (frame-header or
114c167b9c7SMaximilian Luz  * payload) CRC is invalid, or %-EMSGSIZE if the SSH message is bigger than
115c167b9c7SMaximilian Luz  * the maximum message length specified in the @maxlen parameter.
116c167b9c7SMaximilian Luz  */
sshp_parse_frame(const struct device * dev,const struct ssam_span * source,struct ssh_frame ** frame,struct ssam_span * payload,size_t maxlen)117c167b9c7SMaximilian Luz int sshp_parse_frame(const struct device *dev, const struct ssam_span *source,
118c167b9c7SMaximilian Luz 		     struct ssh_frame **frame, struct ssam_span *payload,
119c167b9c7SMaximilian Luz 		     size_t maxlen)
120c167b9c7SMaximilian Luz {
121c167b9c7SMaximilian Luz 	struct ssam_span sf;
122c167b9c7SMaximilian Luz 	struct ssam_span sp;
123c167b9c7SMaximilian Luz 
124c167b9c7SMaximilian Luz 	/* Initialize output. */
125c167b9c7SMaximilian Luz 	*frame = NULL;
126c167b9c7SMaximilian Luz 	payload->ptr = NULL;
127c167b9c7SMaximilian Luz 	payload->len = 0;
128c167b9c7SMaximilian Luz 
129c167b9c7SMaximilian Luz 	if (!sshp_starts_with_syn(source)) {
130c167b9c7SMaximilian Luz 		dev_warn(dev, "rx: parser: invalid start of frame\n");
131c167b9c7SMaximilian Luz 		return -ENOMSG;
132c167b9c7SMaximilian Luz 	}
133c167b9c7SMaximilian Luz 
134c167b9c7SMaximilian Luz 	/* Check for minimum packet length. */
135c167b9c7SMaximilian Luz 	if (unlikely(source->len < SSH_MESSAGE_LENGTH(0))) {
136c167b9c7SMaximilian Luz 		dev_dbg(dev, "rx: parser: not enough data for frame\n");
137c167b9c7SMaximilian Luz 		return 0;
138c167b9c7SMaximilian Luz 	}
139c167b9c7SMaximilian Luz 
140c167b9c7SMaximilian Luz 	/* Pin down frame. */
141c167b9c7SMaximilian Luz 	sf.ptr = source->ptr + sizeof(u16);
142c167b9c7SMaximilian Luz 	sf.len = sizeof(struct ssh_frame);
143c167b9c7SMaximilian Luz 
144c167b9c7SMaximilian Luz 	/* Validate frame CRC. */
145c167b9c7SMaximilian Luz 	if (unlikely(!sshp_validate_crc(&sf, sf.ptr + sf.len))) {
146c167b9c7SMaximilian Luz 		dev_warn(dev, "rx: parser: invalid frame CRC\n");
147c167b9c7SMaximilian Luz 		return -EBADMSG;
148c167b9c7SMaximilian Luz 	}
149c167b9c7SMaximilian Luz 
150c167b9c7SMaximilian Luz 	/* Ensure packet does not exceed maximum length. */
151c167b9c7SMaximilian Luz 	sp.len = get_unaligned_le16(&((struct ssh_frame *)sf.ptr)->len);
152c167b9c7SMaximilian Luz 	if (unlikely(SSH_MESSAGE_LENGTH(sp.len) > maxlen)) {
153c167b9c7SMaximilian Luz 		dev_warn(dev, "rx: parser: frame too large: %llu bytes\n",
154c167b9c7SMaximilian Luz 			 SSH_MESSAGE_LENGTH(sp.len));
155c167b9c7SMaximilian Luz 		return -EMSGSIZE;
156c167b9c7SMaximilian Luz 	}
157c167b9c7SMaximilian Luz 
158c167b9c7SMaximilian Luz 	/* Pin down payload. */
159c167b9c7SMaximilian Luz 	sp.ptr = sf.ptr + sf.len + sizeof(u16);
160c167b9c7SMaximilian Luz 
161c167b9c7SMaximilian Luz 	/* Check for frame + payload length. */
162c167b9c7SMaximilian Luz 	if (source->len < SSH_MESSAGE_LENGTH(sp.len)) {
163c167b9c7SMaximilian Luz 		dev_dbg(dev, "rx: parser: not enough data for payload\n");
164c167b9c7SMaximilian Luz 		return 0;
165c167b9c7SMaximilian Luz 	}
166c167b9c7SMaximilian Luz 
167c167b9c7SMaximilian Luz 	/* Validate payload CRC. */
168c167b9c7SMaximilian Luz 	if (unlikely(!sshp_validate_crc(&sp, sp.ptr + sp.len))) {
169c167b9c7SMaximilian Luz 		dev_warn(dev, "rx: parser: invalid payload CRC\n");
170c167b9c7SMaximilian Luz 		return -EBADMSG;
171c167b9c7SMaximilian Luz 	}
172c167b9c7SMaximilian Luz 
173c167b9c7SMaximilian Luz 	*frame = (struct ssh_frame *)sf.ptr;
174c167b9c7SMaximilian Luz 	*payload = sp;
175c167b9c7SMaximilian Luz 
176c167b9c7SMaximilian Luz 	dev_dbg(dev, "rx: parser: valid frame found (type: %#04x, len: %u)\n",
177c167b9c7SMaximilian Luz 		(*frame)->type, (*frame)->len);
178c167b9c7SMaximilian Luz 
179c167b9c7SMaximilian Luz 	return 0;
180c167b9c7SMaximilian Luz }
181c167b9c7SMaximilian Luz 
182c167b9c7SMaximilian Luz /**
183c167b9c7SMaximilian Luz  * sshp_parse_command() - Parse SSH command frame payload.
184c167b9c7SMaximilian Luz  * @dev: The device used for logging.
185c167b9c7SMaximilian Luz  * @source: The source to parse from.
186c167b9c7SMaximilian Luz  * @command: The parsed command (output).
187c167b9c7SMaximilian Luz  * @command_data: The parsed command data/payload (output).
188c167b9c7SMaximilian Luz  *
189c167b9c7SMaximilian Luz  * Parses and validates a SSH command frame payload. Sets the @command pointer
190c167b9c7SMaximilian Luz  * to the command header and the @command_data span to the command data (i.e.
191c167b9c7SMaximilian Luz  * payload of the command). This will result in a zero-length span if the
192c167b9c7SMaximilian Luz  * command does not have any associated data/payload. This function does not
193c167b9c7SMaximilian Luz  * check the frame-payload-type field, which should be checked by the caller
194c167b9c7SMaximilian Luz  * before calling this function.
195c167b9c7SMaximilian Luz  *
196c167b9c7SMaximilian Luz  * The @source parameter should be the complete frame payload, e.g. returned
197c167b9c7SMaximilian Luz  * by the sshp_parse_frame() command.
198c167b9c7SMaximilian Luz  *
199c167b9c7SMaximilian Luz  * This function does not copy any data, but rather only validates the frame
200c167b9c7SMaximilian Luz  * payload data and sets pointers (and length values) to indicate the
201c167b9c7SMaximilian Luz  * respective parts.
202c167b9c7SMaximilian Luz  *
203c167b9c7SMaximilian Luz  * Return: Returns zero on success or %-ENOMSG if @source does not represent a
204c167b9c7SMaximilian Luz  * valid command-type frame payload, i.e. is too short.
205c167b9c7SMaximilian Luz  */
sshp_parse_command(const struct device * dev,const struct ssam_span * source,struct ssh_command ** command,struct ssam_span * command_data)206c167b9c7SMaximilian Luz int sshp_parse_command(const struct device *dev, const struct ssam_span *source,
207c167b9c7SMaximilian Luz 		       struct ssh_command **command,
208c167b9c7SMaximilian Luz 		       struct ssam_span *command_data)
209c167b9c7SMaximilian Luz {
210c167b9c7SMaximilian Luz 	/* Check for minimum length. */
211c167b9c7SMaximilian Luz 	if (unlikely(source->len < sizeof(struct ssh_command))) {
212c167b9c7SMaximilian Luz 		*command = NULL;
213c167b9c7SMaximilian Luz 		command_data->ptr = NULL;
214c167b9c7SMaximilian Luz 		command_data->len = 0;
215c167b9c7SMaximilian Luz 
216c167b9c7SMaximilian Luz 		dev_err(dev, "rx: parser: command payload is too short\n");
217c167b9c7SMaximilian Luz 		return -ENOMSG;
218c167b9c7SMaximilian Luz 	}
219c167b9c7SMaximilian Luz 
220c167b9c7SMaximilian Luz 	*command = (struct ssh_command *)source->ptr;
221c167b9c7SMaximilian Luz 	command_data->ptr = source->ptr + sizeof(struct ssh_command);
222c167b9c7SMaximilian Luz 	command_data->len = source->len - sizeof(struct ssh_command);
223c167b9c7SMaximilian Luz 
224c167b9c7SMaximilian Luz 	dev_dbg(dev, "rx: parser: valid command found (tc: %#04x, cid: %#04x)\n",
225c167b9c7SMaximilian Luz 		(*command)->tc, (*command)->cid);
226c167b9c7SMaximilian Luz 
227c167b9c7SMaximilian Luz 	return 0;
228c167b9c7SMaximilian Luz }
229