Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
  • 3 commits
  • 6 files changed
  • 0 comments
  • 1 contributor
23  include/irc.h
@@ -6,10 +6,6 @@
6 6
 typedef struct irc_message_s irc_message_t;
7 7
 typedef struct irc_parser_s irc_parser_t;
8 8
 
9  
-typedef enum {
10  
-  IRC_PASSWORD
11  
-} irc_command;
12  
-
13 9
 struct irc_message_s {
14 10
   const char* prefix;
15 11
   const char* command;
@@ -23,18 +19,23 @@ struct irc_message_s {
23 19
  */
24 20
 typedef int (*irc_message_cb)(irc_message_t* msg);
25 21
 
  22
+// An illegal token was encountered while parsing message.
  23
+#define PARSER_ILLEGAL_TOKEN 0x01
  24
+
  25
+// The message has exceeded the 512 byte length limit.
  26
+#define PARSER_MESSAGE_TOO_LONG 0x02
  27
+
26 28
 struct irc_parser_s {
27 29
   irc_message_cb message_cb;
28 30
 
29  
-  /*
30  
-   * The last error that caused the parser to stop.
31  
-   * If a non-zero value was returned from irc_message_cb
32  
-   * this variable will contain that value.
33  
-   */
  31
+  // If an error occurred while parsing a message
  32
+  // this will contain a non-zero error code.
  33
+  // All errors are fatal and the connection providing
  34
+  // the data should be terminated. The parser may only
  35
+  // be executed again if re-initialized to clear the error.
34 36
   int last_error;
35 37
 
36  
-  // --- Private ---
37  
-
  38
+  // Private.
38 39
   unsigned char state;
39 40
   char data[512];
40 41
   int len;
3  shmooze.gyp
@@ -32,7 +32,8 @@
32 32
         'test/runner.h',
33 33
         'test/task.h',
34 34
         'test/test-list.h',
35  
-        'test/test-parse-message.c'
  35
+        'test/test-parse-message.c',
  36
+        'test/test-parse-illegal.c'
36 37
       ]
37 38
     },
38 39
 
42  src/parser.c
@@ -10,6 +10,9 @@ enum parser_state {
10 10
   STATE_END
11 11
 };
12 12
 
  13
+// IRC messages cannot exceed 512 bytes.
  14
+#define MESSAGE_MAX_LENGTH 512
  15
+
13 16
 /*
14 17
  * These macros are provided for accessing the 512 byte
15 18
  * buffer used by the parser for holding message parts.
@@ -22,22 +25,20 @@ enum parser_state {
22 25
 void irc_parser_init(irc_parser_t* parser) {
23 26
   parser->last_error = 0;
24 27
   parser->state = STATE_START;
  28
+  parser->message_cb = 0;
25 29
 }
26 30
 
27 31
 int irc_parser_execute(irc_parser_t* parser, const char* buffer, size_t len) {
28  
-  // Current position in the buffer being parsed.
29  
-  const char* b = buffer;
30  
-
31  
-  // The end position of the buffer.
32  
-  const char* b_end = b + len;
  32
+  // Counts the number of bytes parsed from the buffer.
  33
+  size_t bytes_parsed = 0;
33 34
 
34 35
   unsigned char state = parser->state;
35 36
   irc_message_t* message = &parser->message;
36 37
 
37  
-  while (b != b_end) {
  38
+  while (bytes_parsed < len) {
38 39
   // The main parser loop which runs until buffer end is reached.
39 40
     // Get message character to parse and advance to next byte.
40  
-    char ch = *(b++);
  41
+    char ch = *(buffer + bytes_parsed++);
41 42
 
42 43
     // Process the current message byte based on current state.
43 44
     switch (state) {
@@ -47,15 +48,21 @@ int irc_parser_execute(irc_parser_t* parser, const char* buffer, size_t len) {
47 48
         message->parameter_count = 0;
48 49
         parser->len = 0;
49 50
 
50  
-        if (ch == ':') {
51  
-          // Message has a prefix we need to scan.
52  
-          state = STATE_PREFIX;
53  
-          message->prefix = DATA_END(parser);
54  
-          continue;
  51
+        switch (ch) {
  52
+          case ':':
  53
+            // Message has a prefix we need to scan.
  54
+            state = STATE_PREFIX;
  55
+            message->prefix = DATA_END(parser);
  56
+            continue;
  57
+
  58
+          case ' ':
  59
+            // Illegal to have first byte be a whitespace token.
  60
+            parser->last_error = PARSER_ILLEGAL_TOKEN;
  61
+            return bytes_parsed;
55 62
         }
56  
-        message->command = DATA_END(parser);
57 63
 
58  
-        // If message has no prefix, skip to scanning the command name.
  64
+        // If the message has no prefix, skip to parsing the command.
  65
+        message->command = DATA_END(parser);
59 66
         state = STATE_COMMAND;
60 67
         break;
61 68
 
@@ -127,9 +134,8 @@ int irc_parser_execute(irc_parser_t* parser, const char* buffer, size_t len) {
127 134
 
128 135
       case STATE_END:
129 136
         DATA_NUL(parser);
130  
-        if ((parser->last_error = parser->message_cb(message))) {
131  
-          // TODO: verify this is correct bytes parsed.
132  
-          return b_end - b;
  137
+        if (parser->message_cb && (parser->last_error = parser->message_cb(message))) {
  138
+          return bytes_parsed;
133 139
         }
134 140
         state = STATE_START;
135 141
         continue;
@@ -142,6 +148,6 @@ int irc_parser_execute(irc_parser_t* parser, const char* buffer, size_t len) {
142 148
 
143 149
   parser->state = state;
144 150
 
145  
-  return len;
  151
+  return bytes_parsed;
146 152
 }
147 153
 
4  test/test-list.h
@@ -4,10 +4,14 @@ TEST_DECLARE   (parse_message_middle)
4 4
 TEST_DECLARE   (parse_message_trailing)
5 5
 TEST_DECLARE   (parse_message_mixed)
6 6
 
  7
+TEST_DECLARE   (parse_illegal_first_byte_whitespace)
  8
+
7 9
 TASK_LIST_START
8 10
   TEST_ENTRY  (parse_message_command)
9 11
   TEST_ENTRY  (parse_message_prefix)
10 12
   TEST_ENTRY  (parse_message_middle)
11 13
   TEST_ENTRY  (parse_message_trailing)
12 14
   TEST_ENTRY  (parse_message_mixed)
  15
+
  16
+  TEST_ENTRY  (parse_illegal_first_byte_whitespace)
13 17
 TASK_LIST_END
48  test/test-parse-illegal.c
... ...
@@ -0,0 +1,48 @@
  1
+// A set of tests to verify the parser can handle illegal
  2
+// messages by aborting and reporting error codes. These tests
  3
+// to should guard the parser from exploits such as buffer overflow
  4
+// by feeding it maliciously formatted messages.
  5
+
  6
+#include "task.h"
  7
+
  8
+#include "irc.h"
  9
+
  10
+// Parse the message and returns the last error code.
  11
+static int parse_message(const char* msg) {
  12
+  irc_parser_t parser;
  13
+  irc_parser_init(&parser);
  14
+  irc_parser_execute(&parser, msg, strlen(msg));
  15
+  return parser.last_error;
  16
+}
  17
+
  18
+// Messages cannot have whitespace as the first byte.
  19
+// This should cause an PARSER_ILLEGAL_TOKEN error code.
  20
+TEST_IMPL(parse_illegal_first_byte_whitespace) {
  21
+  ASSERT(parse_message(" BAD\r\n") == PARSER_ILLEGAL_TOKEN);
  22
+  return 0;
  23
+}
  24
+
  25
+// Messages that exceed 512 bytes should cause the parser
  26
+// to abort and return error code PARSER_MESSAGE_TOO_LONG.
  27
+TEST_IMPL(parse_illegal_too_long) {
  28
+  const char* msg = ":Bob PRIVMSG John :";
  29
+  size_t msg_len = strlen(msg);
  30
+  char msg_end[1024];
  31
+  memset(&msg_end, 'a', 1024);
  32
+
  33
+  irc_parser_t parser;
  34
+  irc_parser_init(&parser);
  35
+
  36
+  // Seed the parser with the start of a valid message.
  37
+  ASSERT(irc_parser_execute(&parser, msg, msg_len) == msg_len);
  38
+  ASSERT(parser.last_error == 0);
  39
+
  40
+  // Feed the parser enough valid data to go over the IRC message limit.
  41
+  // It should cause the parser to stop once it reaches 512 bytes parsed.
  42
+  ASSERT(irc_parser_execute(&parser, msg_end, 1024) == (1024 - msg_len));
  43
+  ASSERT(parser.last_error == PARSER_MESSAGE_TOO_LONG);
  44
+  ASSERT(parser.len == 512);
  45
+
  46
+  return 0;
  47
+}
  48
+
2  test/test-parse-message.c
... ...
@@ -1,3 +1,5 @@
  1
+// A set of tests to verify valid IRC messages are parsed correctly.
  2
+
1 3
 #include "irc.h"
2 4
 
3 5
 #include "task.h"

No commit comments for this range

Something went wrong with that request. Please try again.