ssh_parser.c 7.3 KB

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