amqplib 2.3.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Change log for amqplib
2
2
 
3
+ ## v2.3.1
4
+ - Throw a clear `RangeError` from `publish` when an encoded headers or `arguments` table exceeds the 64 KiB encoding buffer. Previously the outcome depended on where the boundary fell: a string or Buffer value crossing the end of the buffer was silently truncated, producing a frame whose table length prefix did not match its contents, which RabbitMQ rejected by closing the connection with a 541 `INTERNAL_ERROR` after `publish` had returned; at other positions a bare `offset is out of range` error escaped from `publish` instead (fixes #867)
5
+
3
6
  ## v2.3.0
4
7
  - Reject a `connection.tune` whose `frameMax` is below the protocol minimum of 4096 (the spec allows 0 or at least frame-min-size). Previously such a value was accepted, and the first publish whose encoded frames total 2048 bytes or more entered a loop in `sendContent` that never advanced, exhausting memory. `sendContent` now also throws if `frameMax` leaves no room for a body frame (GHSA-37f9-7m79-6866, reported by Rizwan Bedekar ([@r-bedekar](https://github.com/r-bedekar)); fix based in part on their patch and tests)
5
8
  - Add a `maxFrameSize` connection option, also settable as a URL query parameter, bounding the payload size of frames accepted from the server. The default is 64 MiB, or the negotiated `frameMax` if that is larger, so raising `frameMax` needs no second option; `0` disables the limit. Incoming frame sizes were previously unchecked, so a frame header declaring up to 4 GiB made the client buffer, and repeatedly re-copy, that much data before looking at it. An oversized frame is now refused from its header alone and closes the connection with `Frame size N exceeds maxFrameSize M`. The limit is deliberately separate from the negotiated `frameMax`, since some brokers send larger frames without splitting them (see #784). **Upgrade note:** a broker that delivers an unsplit frame larger than the limit will now have that connection closed; pass `maxFrameSize: 0`, or a higher value, to restore the previous behaviour (GHSA-37f9-7m79-6866)
package/lib/codec.js CHANGED
@@ -62,12 +62,20 @@ function isFloatingPoint(n) {
62
62
  return n >= 0x8000000000000000 || (Math.abs(n) < 0x4000000000000 && Math.floor(n) !== n);
63
63
  }
64
64
 
65
+ function checkSpace(buffer, offset, size) {
66
+ if (offset + size > buffer.length) {
67
+ throw new RangeError(`Encoded field table (headers or arguments) exceeds the ${buffer.length} byte encoding buffer`);
68
+ }
69
+ }
70
+
65
71
  function encodeTable(buffer, val, offset) {
66
72
  const start = offset;
73
+ checkSpace(buffer, offset, 4);
67
74
  offset += 4; // leave room for the table length
68
75
  for (const key in val) {
69
76
  if (val[key] !== undefined) {
70
77
  const len = Buffer.byteLength(key);
78
+ checkSpace(buffer, offset, 1 + len);
71
79
  buffer.writeUInt8(len, offset);
72
80
  offset++;
73
81
  buffer.write(key, offset, 'utf8');
@@ -82,6 +90,7 @@ function encodeTable(buffer, val, offset) {
82
90
 
83
91
  function encodeArray(buffer, val, offset) {
84
92
  const start = offset;
93
+ checkSpace(buffer, offset, 4);
85
94
  offset += 4;
86
95
  for (let i = 0, num = val.length; i < num; i++) {
87
96
  offset += encodeFieldValue(buffer, val[i], offset);
@@ -133,6 +142,7 @@ function encodeFieldValue(buffer, value, offset) {
133
142
  }
134
143
 
135
144
  function tag(t) {
145
+ checkSpace(buffer, offset, 1);
136
146
  buffer.write(t, offset);
137
147
  offset++;
138
148
  }
@@ -142,6 +152,7 @@ function encodeFieldValue(buffer, value, offset) {
142
152
  // no shortstr in field tables
143
153
  const len = Buffer.byteLength(val, 'utf8');
144
154
  tag('S');
155
+ checkSpace(buffer, offset, 4 + len);
145
156
  buffer.writeUInt32BE(len, offset);
146
157
  offset += 4;
147
158
  buffer.write(val, offset, 'utf8');
@@ -156,6 +167,7 @@ function encodeFieldValue(buffer, value, offset) {
156
167
  offset += encodeArray(buffer, val, offset);
157
168
  } else if (Buffer.isBuffer(val)) {
158
169
  tag('x');
170
+ checkSpace(buffer, offset, 4 + val.length);
159
171
  buffer.writeUInt32BE(val.length, offset);
160
172
  offset += 4;
161
173
  val.copy(buffer, offset);
@@ -167,6 +179,7 @@ function encodeFieldValue(buffer, value, offset) {
167
179
  break;
168
180
  case 'boolean':
169
181
  tag('t');
182
+ checkSpace(buffer, offset, 1);
170
183
  buffer.writeUInt8(val ? 1 : 0, offset);
171
184
  offset++;
172
185
  break;
@@ -175,48 +188,56 @@ function encodeFieldValue(buffer, value, offset) {
175
188
  case 'double':
176
189
  case 'float64':
177
190
  tag('d');
191
+ checkSpace(buffer, offset, 8);
178
192
  buffer.writeDoubleBE(val, offset);
179
193
  offset += 8;
180
194
  break;
181
195
  case 'byte':
182
196
  case 'int8':
183
197
  tag('b');
198
+ checkSpace(buffer, offset, 1);
184
199
  buffer.writeInt8(val, offset);
185
200
  offset++;
186
201
  break;
187
202
  case 'unsignedbyte':
188
203
  case 'uint8':
189
204
  tag('B');
205
+ checkSpace(buffer, offset, 1);
190
206
  buffer.writeUInt8(val, offset);
191
207
  offset++;
192
208
  break;
193
209
  case 'short':
194
210
  case 'int16':
195
211
  tag('s');
212
+ checkSpace(buffer, offset, 2);
196
213
  buffer.writeInt16BE(val, offset);
197
214
  offset += 2;
198
215
  break;
199
216
  case 'unsignedshort':
200
217
  case 'uint16':
201
218
  tag('u');
219
+ checkSpace(buffer, offset, 2);
202
220
  buffer.writeUInt16BE(val, offset);
203
221
  offset += 2;
204
222
  break;
205
223
  case 'int':
206
224
  case 'int32':
207
225
  tag('I');
226
+ checkSpace(buffer, offset, 4);
208
227
  buffer.writeInt32BE(val, offset);
209
228
  offset += 4;
210
229
  break;
211
230
  case 'unsignedint':
212
231
  case 'uint32':
213
232
  tag('i');
233
+ checkSpace(buffer, offset, 4);
214
234
  buffer.writeUInt32BE(val, offset);
215
235
  offset += 4;
216
236
  break;
217
237
  case 'long':
218
238
  case 'int64':
219
239
  tag('l');
240
+ checkSpace(buffer, offset, 8);
220
241
  buffer.writeBigInt64BE(BigInt(val), offset);
221
242
  offset += 8;
222
243
  break;
@@ -225,17 +246,20 @@ function encodeFieldValue(buffer, value, offset) {
225
246
  // `{'!': type, value: val}
226
247
  case 'timestamp':
227
248
  tag('T');
249
+ checkSpace(buffer, offset, 8);
228
250
  buffer.writeBigUInt64BE(BigInt(val), offset);
229
251
  offset += 8;
230
252
  break;
231
253
  case 'float':
232
254
  tag('f');
255
+ checkSpace(buffer, offset, 4);
233
256
  buffer.writeFloatBE(val, offset);
234
257
  offset += 4;
235
258
  break;
236
259
  case 'decimal':
237
260
  tag('D');
238
261
  if (Object.hasOwn(val, 'places') && Object.hasOwn(val, 'digits') && val.places >= 0 && val.places < 256) {
262
+ checkSpace(buffer, offset, 5);
239
263
  buffer[offset] = val.places;
240
264
  offset++;
241
265
  buffer.writeUInt32BE(val.digits, offset);
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "amqplib",
3
3
  "homepage": "http://amqp-node.github.io/amqplib/",
4
4
  "main": "./channel_api.js",
5
- "version": "2.3.0",
5
+ "version": "2.3.1",
6
6
  "types": "./index.d.ts",
7
7
  "exports": {
8
8
  ".": {