libmongocrypt-helper 1.20.1.0.1001 → 1.20.5.0.1001

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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/ext/libmongocrypt/libmongocrypt/CHANGELOG.md +21 -0
  3. data/ext/libmongocrypt/libmongocrypt/Earthfile +4 -3
  4. data/ext/libmongocrypt/libmongocrypt/bindings/python/CONTRIBUTING.md +12 -0
  5. data/ext/libmongocrypt/libmongocrypt/bindings/python/scripts/update-version.sh +1 -1
  6. data/ext/libmongocrypt/libmongocrypt/doc/releasing.md +10 -0
  7. data/ext/libmongocrypt/libmongocrypt/etc/cyclonedx.sbom.json +2 -2
  8. data/ext/libmongocrypt/libmongocrypt/kms-message/src/kms_gcp_request.c +42 -0
  9. data/ext/libmongocrypt/libmongocrypt/src/mc-fle2-insert-update-payload-v2.c +6 -1
  10. data/ext/libmongocrypt/libmongocrypt/src/mc-fle2-insert-update-payload.c +6 -1
  11. data/ext/libmongocrypt/libmongocrypt/src/mc-fle2-payload-iev.c +10 -2
  12. data/ext/libmongocrypt/libmongocrypt/src/mc-fle2-payload-uev-common.c +1 -1
  13. data/ext/libmongocrypt/libmongocrypt/src/mc-schema-broker.c +10 -2
  14. data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-crypto.c +7 -3
  15. data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-ctx-decrypt.c +28 -1
  16. data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-ctx-encrypt.c +25 -4
  17. data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-endpoint.c +122 -10
  18. data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-kek.c +19 -7
  19. data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-key.c +4 -0
  20. data/ext/libmongocrypt/libmongocrypt/src/mongocrypt-private.h +5 -0
  21. data/ext/libmongocrypt/libmongocrypt/src/mongocrypt.c +11 -0
  22. data/ext/libmongocrypt/libmongocrypt/test/data/kek-tests.json +3 -3
  23. data/ext/libmongocrypt/libmongocrypt/test/test-gcp-auth.c +51 -0
  24. data/ext/libmongocrypt/libmongocrypt/test/test-mc-fle2-payload-iup-v2.c +64 -0
  25. data/ext/libmongocrypt/libmongocrypt/test/test-mc-fle2-payload-iup.c +67 -0
  26. data/ext/libmongocrypt/libmongocrypt/test/test-mongocrypt-ctx-decrypt.c +46 -20
  27. data/ext/libmongocrypt/libmongocrypt/test/test-mongocrypt-ctx-encrypt.c +114 -0
  28. data/ext/libmongocrypt/libmongocrypt/test/test-mongocrypt-endpoint.c +170 -0
  29. data/ext/libmongocrypt/libmongocrypt/test/test-mongocrypt-kek.c +36 -0
  30. data/ext/libmongocrypt/libmongocrypt/test/test-mongocrypt-key.c +26 -0
  31. data/lib/libmongocrypt_helper/version.rb +2 -2
  32. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f3bd46869436cf55ec31236a73965ec1f198f75809d53e084aebb6c056bead0
4
- data.tar.gz: 4868779b572f103a7f7f6ae88ea15032dd829e31c5a85e1f41364bd0d7f17d60
3
+ metadata.gz: b483fc7b479e693858209265fa305b4bd51e34592b452bbbd36c32c13f262802
4
+ data.tar.gz: 352d1052b07f14081865b5c02fe8b33ce2b0a739550db43a6e57d6c26cd1a08b
5
5
  SHA512:
6
- metadata.gz: 57f84ed485336784bd0dd5008a4d2f893146f3cb4128c121d5e91dc5bb8c80c07f8d7a6e44edd869327876c1b0410b1790b84a0548bb0459b6e7bc53a7151a83
7
- data.tar.gz: 27917b4a2c97d98db62661d48f0c3dace264f84f83ba9fd1f9de351dc11a20bb0f29445c51ffa94b82b51716adb1bf925bf18804358d5bac1b701651bc735846
6
+ metadata.gz: 945910ccf2515b64cbd9dd1d97a9de2822562a2544533006e3aa5fa7837107697f6b38e5f43d0da1ce3218fa98d36362a9f883362d9e1efb16bb90fbee7d5236
7
+ data.tar.gz: ef89918bdf09d579c60cce8aa5ecdd779a655edffc7223fef5a97fc00fa63ef787c3d5b360ce880812dc3d96562c1ebda7ca158ba4968377e1fd95fcb9c9a81c
@@ -1,5 +1,26 @@
1
1
  # ChangeLog
2
2
 
3
+ ## 1.20.5
4
+
5
+ ### Fixed
6
+ - Fix possible abort parsing endpoints.
7
+ - Reject more payloads that cannot be decrypted in explicit decryption.
8
+ - Fix more degenerate cases of masterKey parsing.
9
+
10
+ ## 1.20.4
11
+
12
+ ### Fixed
13
+ - Reject undersized decryption payloads.
14
+
15
+ ## 1.20.3
16
+
17
+ ### Fixed
18
+ - Validate database and collection name arguments against "." and NUL bytes
19
+ ## 1.20.2
20
+
21
+ ### Changed
22
+ - Add checks when creating GCP requests.
23
+
3
24
  ## 1.20.1
4
25
 
5
26
  ## Changed
@@ -438,12 +438,12 @@ test-deb-packages-from-ppa:
438
438
  RUN ./test.out
439
439
 
440
440
  # `sign` uses Garasign to sign a file with the libmongocrypt key.
441
- # Requires prior authentication with Artifactory.
441
+ # Requires prior authentication with the DevProd Platforms ECR registry (see doc/releasing.md).
442
442
  # See: https://docs.devprod.prod.corp.mongodb.com/release-tools-container-images/garasign/garasign_signing/.
443
443
  sign:
444
444
  ARG --required file_to_sign
445
445
  ARG --required output_file
446
- FROM artifactory.corp.mongodb.com/release-tools-container-registry-local/garasign-gpg
446
+ FROM 901841024863.dkr.ecr.us-east-1.amazonaws.com/release-infrastructure/garasign-gpg
447
447
  WORKDIR /s
448
448
  COPY ${file_to_sign} /s/file
449
449
  RUN --secret garasign_username --secret garasign_password \
@@ -460,8 +460,9 @@ sign:
460
460
  # An environment with the `silkbomb` command.
461
461
  #
462
462
  # See https://docs.devprod.prod.corp.mongodb.com/mms/python/src/sbom/silkbomb/ for documentation of silkbomb.
463
+ # Requires prior authentication with the DevProd Platforms ECR registry (see doc/releasing.md).
463
464
  silkbomb:
464
- FROM artifactory.corp.mongodb.com/release-tools-container-registry-public-local/silkbomb:2.0
465
+ FROM 901841024863.dkr.ecr.us-east-1.amazonaws.com/release-infrastructure/silkbomb:2.0
465
466
  # Alias the silkbomb executable to a simpler name:
466
467
  RUN ln -s /python/src/sbom/silkbomb/bin /usr/local/bin/silkbomb
467
468
 
@@ -28,6 +28,18 @@ bash scripts/update-version.sh <new-version>
28
28
  This will set the version in `scripts/libmongocrypt-version.sh` and update `sbom.json` to reflect
29
29
  the new vendored version of `libmongocrypt`.
30
30
 
31
+ This pulls the `silkbomb` image from the DevProd Platforms ECR registry. Authenticate to the ECR
32
+ registry with the container launcher (`docker` or `podman`) before running the script:
33
+
34
+ ```bash
35
+ aws sso login --profile ECRScopedAccess-901841024863
36
+
37
+ # Authenticate "podman" or "docker"
38
+ CONTAINER_LAUNCHER="podman"
39
+
40
+ aws ecr get-login-password --region us-east-1 --profile ECRScopedAccess-901841024863 | $CONTAINER_LAUNCHER login --username AWS --password-stdin 901841024863.dkr.ecr.us-east-1.amazonaws.com
41
+ ```
42
+
31
43
  ## Building wheels
32
44
 
33
45
  To build wheels, run `scripts/release.sh`. It will build the appropriate wheel for the current system
@@ -21,7 +21,7 @@ else
21
21
  fi
22
22
 
23
23
  echo "pkg:github/mongodb/libmongocrypt@$LIBMONGOCRYPT_VERSION" > purls.txt
24
- $DOCKER run --platform="linux/amd64" -it --rm -v $(pwd):$(pwd) artifactory.corp.mongodb.com/release-tools-container-registry-public-local/silkbomb:2.0 update --purls=$(pwd)/purls.txt -o $(pwd)/sbom.json
24
+ $DOCKER run --platform="linux/amd64" -it --rm -v $(pwd):$(pwd) 901841024863.dkr.ecr.us-east-1.amazonaws.com/release-infrastructure/silkbomb:2.0 update --purls=$(pwd)/purls.txt -o $(pwd)/sbom.json
25
25
  rm purls.txt
26
26
 
27
27
  popd
@@ -93,6 +93,15 @@ Do the following when releasing:
93
93
  ```bash
94
94
  ./.evergreen/earthly.sh +sbom-generate-new-serial-number
95
95
  ```
96
+ This pulls the `silkbomb` image from the DevProd Platforms ECR registry. Authenticate to the ECR registry with the container launcher (`docker` or `podman`):
97
+ ```bash
98
+ aws sso login --profile ECRScopedAccess-901841024863
99
+
100
+ # Authenticate "podman" or "docker"
101
+ CONTAINER_LAUNCHER="podman"
102
+
103
+ aws ecr get-login-password --region us-east-1 --profile ECRScopedAccess-901841024863 | $CONTAINER_LAUNCHER login --username AWS --password-stdin 901841024863.dkr.ecr.us-east-1.amazonaws.com
104
+ ```
96
105
  Commit resulting `etc/cyclonedx.sbom.json` and push to `rx.y`.
97
106
  - Remove yourself from the [releases team](https://github.com/orgs/mongodb/teams/dbx-c-cxx-releases) on GitHub via [MANA](https://mana.corp.mongodb.com/resources/68029673d39aa9f7de6399f9).
98
107
  - If this is a new non-patch release (e.g. `x.y.0`):
@@ -130,6 +139,7 @@ Do the following when releasing:
130
139
  ```bash
131
140
  ./.evergreen/earthly.sh +sbom-generate-new-serial-number
132
141
  ```
142
+ Requires AWS SSO access to the `ECRScopedAccess-901841024863` profile (see note above).
133
143
  Commit resulting `etc/cyclonedx.sbom.json`.
134
144
  - Update the release on the [Jira releases page](https://jira.mongodb.org/projects/MONGOCRYPT/versions).
135
145
  - Record the release on [C/C++ Release Info](https://docs.google.com/spreadsheets/d/1yHfGmDnbA5-Qt8FX4tKWC5xk9AhzYZx1SKF4AD36ecY/edit?usp=sharing). This is done to leave commentary about the process.
@@ -57,7 +57,7 @@
57
57
  }
58
58
  ],
59
59
  "metadata": {
60
- "timestamp": "2026-07-14T13:56:19.068748+00:00",
60
+ "timestamp": "2026-09-03T12:34:15.836217+00:00",
61
61
  "tools": [
62
62
  {
63
63
  "externalReferences": [
@@ -100,7 +100,7 @@
100
100
  }
101
101
  ]
102
102
  },
103
- "serialNumber": "urn:uuid:870a42e2-38df-4271-afee-8ffa5c28bcf7",
103
+ "serialNumber": "urn:uuid:f38989f9-86cb-4a6e-94b5-bbcbf0a8fc1f",
104
104
  "version": 1,
105
105
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
106
106
  "bomFormat": "CycloneDX",
@@ -20,6 +20,8 @@
20
20
  #include "kms_message_private.h"
21
21
  #include "kms_request_opt_private.h"
22
22
 
23
+ #include <ctype.h>
24
+
23
25
  /* Set a default expiration of 5 minutes for JSON Web Tokens (GCP allows up to
24
26
  * one hour) */
25
27
  #define JWT_EXPIRATION_SECS 5 * 60
@@ -145,6 +147,38 @@ done:
145
147
  return req;
146
148
  }
147
149
 
150
+ /* _check_key_identifier returns true if `value` is safe to use as a URL path segment.
151
+ *
152
+ * Key identifiers may originate from key vault documents or caller-supplied options. They are
153
+ * formatted into the request path, so an unescaped '/', '?', '#', space, or CR/LF would rewrite
154
+ * the request target or inject header lines into the request.
155
+ *
156
+ * Only the RFC 3986 unreserved characters are permitted. This is a superset of what GCP itself
157
+ * accepts, so it does not reject any identifier that would otherwise work:
158
+ * - keyRing and keyName must match `[a-zA-Z0-9_-]{1,63}`. See
159
+ * https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings/create and
160
+ * https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys/create
161
+ * - projectId is 6-30 characters of lowercase letters, numbers, and hyphens. See
162
+ * https://cloud.google.com/resource-manager/docs/creating-managing-projects
163
+ * - location is a Cloud location name (e.g. "global", "us-east1").
164
+ * - keyVersion is a positive integer.
165
+ *
166
+ * `value` is not included in the error message: it is untrusted, and the error may be logged. */
167
+ static bool
168
+ _check_key_identifier (kms_request_t *req, const char *name, const char *value)
169
+ {
170
+ const char *c;
171
+
172
+ for (c = value; *c != '\0'; c++) {
173
+ if (!isalnum ((unsigned char) *c) && *c != '-' && *c != '.' && *c != '_' &&
174
+ *c != '~') {
175
+ KMS_ERROR (req, "Invalid character in GCP KMS key identifier: %s", name);
176
+ return false;
177
+ }
178
+ }
179
+ return true;
180
+ }
181
+
148
182
  static kms_request_t *
149
183
  _encrypt_decrypt_common (const char *encrypt_decrypt,
150
184
  const char *host,
@@ -192,6 +226,14 @@ _encrypt_decrypt_common (const char *encrypt_decrypt,
192
226
  goto done;
193
227
  }
194
228
 
229
+ if (!_check_key_identifier (req, "projectId", project_id) ||
230
+ !_check_key_identifier (req, "location", location) ||
231
+ !_check_key_identifier (req, "keyRing", key_ring_name) ||
232
+ !_check_key_identifier (req, "keyName", key_name) ||
233
+ (key_version && !_check_key_identifier (req, "keyVersion", key_version))) {
234
+ goto done;
235
+ }
236
+
195
237
  value_base64 = kms_message_raw_to_b64 (value, value_len);
196
238
  if (!value_base64) {
197
239
  KMS_ERROR (req, "Could not bases64-encode plaintext");
@@ -523,7 +523,12 @@ const _mongocrypt_buffer_t *mc_FLE2InsertUpdatePayloadV2_decrypt(_mongocrypt_cry
523
523
  return NULL;
524
524
  }
525
525
 
526
- _mongocrypt_buffer_resize(&iup->plaintext, fle2v2->get_plaintext_len(ciphertext.len, status));
526
+ const uint32_t plaintext_len = fle2v2->get_plaintext_len(ciphertext.len, status);
527
+ if (!mongocrypt_status_ok(status)) {
528
+ return NULL;
529
+ }
530
+
531
+ _mongocrypt_buffer_resize(&iup->plaintext, plaintext_len);
527
532
  uint32_t bytes_written;
528
533
 
529
534
  if (!fle2v2->do_decrypt(crypto, &iup->userKeyId, user_key, &ciphertext, &iup->plaintext, &bytes_written, status)) {
@@ -252,7 +252,12 @@ const _mongocrypt_buffer_t *mc_FLE2InsertUpdatePayload_decrypt(_mongocrypt_crypt
252
252
  return NULL;
253
253
  }
254
254
 
255
- _mongocrypt_buffer_resize(&iup->plaintext, fle2aead->get_plaintext_len(ciphertext.len, status));
255
+ const uint32_t plaintext_len = fle2aead->get_plaintext_len(ciphertext.len, status);
256
+ if (!mongocrypt_status_ok(status)) {
257
+ return NULL;
258
+ }
259
+
260
+ _mongocrypt_buffer_resize(&iup->plaintext, plaintext_len);
256
261
  uint32_t bytes_written; /* ignored */
257
262
 
258
263
  if (!fle2aead
@@ -353,7 +353,11 @@ static bool mc_FLE2IndexedEncryptedValue_decrypt(_mongocrypt_crypto_t *crypto,
353
353
  const _mongocrypt_buffer_t *token_buf = mc_ServerDataEncryptionLevel1Token_get(token);
354
354
  uint32_t bytes_written;
355
355
 
356
- _mongocrypt_buffer_resize(&iev->Inner, fle2alg->get_plaintext_len(iev->InnerEncrypted.len, status));
356
+ const uint32_t inner_len = fle2alg->get_plaintext_len(iev->InnerEncrypted.len, status);
357
+ if (!mongocrypt_status_ok(status)) {
358
+ return false;
359
+ }
360
+ _mongocrypt_buffer_resize(&iev->Inner, inner_len);
357
361
 
358
362
  /* Decrypt InnerEncrypted. */
359
363
  if (!fle2alg->do_decrypt(crypto,
@@ -445,7 +449,11 @@ bool mc_FLE2IndexedEncryptedValue_add_K_Key(_mongocrypt_crypto_t *crypto,
445
449
  return false;
446
450
  }
447
451
  /* Attempt to decrypt ClientEncryptedValue */
448
- _mongocrypt_buffer_resize(&iev->ClientValue, fle2aead->get_plaintext_len(iev->ClientEncryptedValue.len, status));
452
+ const uint32_t client_value_len = fle2aead->get_plaintext_len(iev->ClientEncryptedValue.len, status);
453
+ if (!mongocrypt_status_ok(status)) {
454
+ return false;
455
+ }
456
+ _mongocrypt_buffer_resize(&iev->ClientValue, client_value_len);
449
457
  uint32_t bytes_written;
450
458
  if (!fle2aead->do_decrypt(crypto,
451
459
  &iev->K_KeyId,
@@ -93,7 +93,7 @@ const _mongocrypt_buffer_t *_mc_FLE2UnindexedEncryptedValueCommon_decrypt(_mongo
93
93
  memcpy(AD.data + 1, key_uuid->data, key_uuid->len);
94
94
  AD.data[1 + key_uuid->len] = (uint8_t)original_bson_type;
95
95
  const uint32_t plaintext_len = fle2aead->get_plaintext_len(ciphertext->len, status);
96
- if (plaintext_len == 0) {
96
+ if (!mongocrypt_status_ok(status)) {
97
97
  _mongocrypt_buffer_cleanup(&AD);
98
98
  return NULL;
99
99
  }
@@ -323,7 +323,11 @@ bool mc_schema_broker_satisfy_from_collinfo(mc_schema_broker_t *sb,
323
323
  CLIENT_ERR("failed to find 'name' in collinfo in database: %s", sb->db);
324
324
  return false;
325
325
  }
326
- coll = bson_iter_utf8(&name_iter, NULL);
326
+ uint32_t coll_len;
327
+ coll = bson_iter_utf8(&name_iter, &coll_len);
328
+ if (!_mongocrypt_check_no_embedded_nul(coll, coll_len, "collection name in collinfo", status)) {
329
+ return false;
330
+ }
327
331
  }
328
332
 
329
333
  // Cache the received collinfo.
@@ -508,7 +512,11 @@ bool mc_schema_broker_satisfy_from_create_or_collMod(mc_schema_broker_t *sb,
508
512
  CLIENT_ERR("Failed to get collection name from command");
509
513
  return false;
510
514
  }
511
- const char *coll = bson_iter_utf8(&iter, NULL);
515
+ uint32_t coll_len;
516
+ const char *coll = bson_iter_utf8(&iter, &coll_len);
517
+ if (!_mongocrypt_check_no_embedded_nul(coll, coll_len, "collection name", status)) {
518
+ return false;
519
+ }
512
520
 
513
521
  // Check if schema was requested.
514
522
  mc_schema_entry_t *found = NULL;
@@ -443,9 +443,9 @@ static uint32_t _mongocrypt_calculate_plaintext_len(uint32_t inlen,
443
443
  _mongocrypt_hmac_type_t hmac,
444
444
  mongocrypt_status_t *status) {
445
445
  const uint32_t hmaclen = (hmac == HMAC_NONE) ? 0 : MONGOCRYPT_HMAC_LEN;
446
- const uint32_t mincipher = (mode == MODE_CTR) ? 0 : MONGOCRYPT_BLOCK_SIZE;
446
+ const uint32_t mincipher = (mode == MODE_CTR) ? 1 : MONGOCRYPT_BLOCK_SIZE;
447
447
  if (inlen < (MONGOCRYPT_IV_LEN + mincipher + hmaclen)) {
448
- CLIENT_ERR("input ciphertext too small. Must be at least %" PRIu32 " bytes",
448
+ CLIENT_ERR("input ciphertext too small. Must be more than %" PRIu32 " bytes",
449
449
  MONGOCRYPT_IV_LEN + mincipher + hmaclen);
450
450
  return 0;
451
451
  }
@@ -1346,7 +1346,11 @@ bool _mongocrypt_unwrap_key(_mongocrypt_crypto_t *crypto,
1346
1346
 
1347
1347
  // _mongocrypt_wrap_key() uses FLE1 algorithm parameters.
1348
1348
  _mongocrypt_buffer_init(dek);
1349
- _mongocrypt_buffer_resize(dek, fle1alg->get_plaintext_len(encrypted_dek->len, status));
1349
+ const uint32_t plaintext_len = fle1alg->get_plaintext_len(encrypted_dek->len, status);
1350
+ if (!mongocrypt_status_ok(status)) {
1351
+ return false;
1352
+ }
1353
+ _mongocrypt_buffer_resize(dek, plaintext_len);
1350
1354
 
1351
1355
  if (!fle1alg->do_decrypt(crypto, NULL /* associated data. */, kek, encrypted_dek, dek, &bytes_written, status)) {
1352
1356
  return false;
@@ -344,7 +344,7 @@ static bool _replace_FLE1Payload_with_plaintext(void *ctx,
344
344
 
345
345
  const _mongocrypt_value_encryption_algorithm_t *fle1alg = _mcFLE1Algorithm();
346
346
  plaintext.len = fle1alg->get_plaintext_len(ciphertext.data.len, status);
347
- CHECK_AND_RETURN(plaintext.len != 0);
347
+ CHECK_AND_RETURN(mongocrypt_status_ok(status));
348
348
  plaintext.data = bson_malloc0(plaintext.len);
349
349
  BSON_ASSERT(plaintext.data);
350
350
 
@@ -766,6 +766,25 @@ static void _cleanup(mongocrypt_ctx_t *ctx) {
766
766
  _mongocrypt_buffer_cleanup(&dctx->decrypted_doc);
767
767
  }
768
768
 
769
+ /* Returns true if a subtype-6 payload's first byte names a recognized FLE blob
770
+ * subtype. */
771
+ static bool _is_decryptable_fle_blob_subtype(uint8_t byte) {
772
+ switch (byte) {
773
+ case MC_SUBTYPE_FLE1DeterministicEncryptedValue:
774
+ case MC_SUBTYPE_FLE1RandomEncryptedValue:
775
+ case MC_SUBTYPE_FLE2InsertUpdatePayload:
776
+ case MC_SUBTYPE_FLE2UnindexedEncryptedValue:
777
+ case MC_SUBTYPE_FLE2IndexedEqualityEncryptedValue:
778
+ case MC_SUBTYPE_FLE2IndexedRangeEncryptedValue:
779
+ case MC_SUBTYPE_FLE2InsertUpdatePayloadV2:
780
+ case MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2:
781
+ case MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2:
782
+ case MC_SUBTYPE_FLE2UnindexedEncryptedValueV2:
783
+ case MC_SUBTYPE_FLE2IndexedTextEncryptedValue: return true;
784
+ default: return false;
785
+ }
786
+ }
787
+
769
788
  bool mongocrypt_ctx_explicit_decrypt_init(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *msg) {
770
789
  bson_iter_t iter;
771
790
  bson_t as_bson;
@@ -804,6 +823,14 @@ bool mongocrypt_ctx_explicit_decrypt_init(mongocrypt_ctx_t *ctx, mongocrypt_bina
804
823
  CLIENT_ERR("decryption expected BSON binary subtype %d, got %d", (int)BSON_SUBTYPE_ENCRYPTED, (int)subtype);
805
824
  return _mongocrypt_ctx_fail(ctx);
806
825
  }
826
+ if (binary_len == 0) {
827
+ CLIENT_ERR("unsupported FLE blob subtype: expected non-empty payload");
828
+ return _mongocrypt_ctx_fail(ctx);
829
+ }
830
+ if (!_is_decryptable_fle_blob_subtype(binary[0])) {
831
+ CLIENT_ERR("unsupported FLE blob subtype: %hhu", binary[0]);
832
+ return _mongocrypt_ctx_fail(ctx);
833
+ }
807
834
  }
808
835
 
809
836
  if (!mongocrypt_ctx_decrypt_init(ctx, msg)) {
@@ -2303,7 +2303,11 @@ static bool _check_cmd_for_auto_encrypt_bulkWrite(mongocrypt_binary_t *cmd,
2303
2303
  return false;
2304
2304
  }
2305
2305
 
2306
- const char *target_ns = bson_iter_utf8(&ns_iter, NULL /* length */);
2306
+ uint32_t target_ns_len;
2307
+ const char *target_ns = bson_iter_utf8(&ns_iter, &target_ns_len);
2308
+ if (!_mongocrypt_check_no_embedded_nul(target_ns, target_ns_len, "namespace in `bulkWrite` command", status)) {
2309
+ return false;
2310
+ }
2307
2311
  // Parse `target_ns` into "<db>.<coll>"
2308
2312
  const char *dot = strstr(target_ns, ".");
2309
2313
  if (!dot) {
@@ -2375,7 +2379,12 @@ _check_cmd_for_auto_encrypt(mongocrypt_binary_t *cmd, bool *bypass, char **targe
2375
2379
  }
2376
2380
 
2377
2381
  if (BSON_ITER_HOLDS_UTF8(&target_coll_iter)) {
2378
- *target_coll = bson_strdup(bson_iter_utf8(&target_coll_iter, NULL));
2382
+ uint32_t target_coll_len;
2383
+ const char *target_coll_str = bson_iter_utf8(&target_coll_iter, &target_coll_len);
2384
+ if (!_mongocrypt_check_no_embedded_nul(target_coll_str, target_coll_len, "collection name", status)) {
2385
+ return false;
2386
+ }
2387
+ *target_coll = bson_strdup(target_coll_str);
2379
2388
  } else {
2380
2389
  *target_coll = NULL;
2381
2390
  }
@@ -2572,7 +2581,11 @@ static bool find_collections_in_pipeline(mc_schema_broker_t *sb,
2572
2581
  stage_key);
2573
2582
  return false;
2574
2583
  }
2575
- const char *from = bson_iter_utf8(&lookup_iter, NULL);
2584
+ uint32_t from_len;
2585
+ const char *from = bson_iter_utf8(&lookup_iter, &from_len);
2586
+ if (!_mongocrypt_check_no_embedded_nul(from, from_len, "'from' collection name", status)) {
2587
+ return false;
2588
+ }
2576
2589
  if (!mc_schema_broker_request(sb, db, from, status)) {
2577
2590
  return false;
2578
2591
  }
@@ -2631,7 +2644,11 @@ static bool find_collections_in_pipeline(mc_schema_broker_t *sb,
2631
2644
  stage_key);
2632
2645
  return false;
2633
2646
  }
2634
- const char *coll = bson_iter_utf8(&unionWith_iter, NULL);
2647
+ uint32_t coll_len;
2648
+ const char *coll = bson_iter_utf8(&unionWith_iter, &coll_len);
2649
+ if (!_mongocrypt_check_no_embedded_nul(coll, coll_len, "'coll' collection name", status)) {
2650
+ return false;
2651
+ }
2635
2652
  if (!mc_schema_broker_request(sb, db, coll, status)) {
2636
2653
  return false;
2637
2654
  }
@@ -2722,6 +2739,10 @@ bool mongocrypt_ctx_encrypt_init(mongocrypt_ctx_t *ctx, const char *db, int32_t
2722
2739
  return _mongocrypt_ctx_fail_w_msg(ctx, "invalid db");
2723
2740
  }
2724
2741
 
2742
+ if (strchr(ectx->cmd_db, '.')) {
2743
+ return _mongocrypt_ctx_fail_w_msg(ctx, "invalid db: must not contain a dot");
2744
+ }
2745
+
2725
2746
  if (0 == strcmp(ectx->cmd_name, "bulkWrite")) {
2726
2747
  // Handle `bulkWrite` as a special case.
2727
2748
  // `bulkWrite` includes the target namespaces in an `nsInfo` field.
@@ -34,6 +34,104 @@ void _mongocrypt_endpoint_destroy(_mongocrypt_endpoint_t *endpoint) {
34
34
  bson_free(endpoint);
35
35
  }
36
36
 
37
+ /* RFC 3986 section 2.3: unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" */
38
+ static bool _is_unreserved(char c) {
39
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.'
40
+ || c == '_' || c == '~';
41
+ }
42
+
43
+ /* RFC 3986 section 2.2: reserved = gen-delims / sub-delims */
44
+ static bool _is_reserved(char c) {
45
+ return NULL
46
+ != strchr(":/?#[]@" /* gen-delims */
47
+ "!$&'()*+,;=" /* sub-delims */,
48
+ c);
49
+ }
50
+
51
+ static bool _is_hexdig(char c) {
52
+ return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
53
+ }
54
+
55
+ /* _endpoint_chars_are_valid returns true if `endpoint_raw` contains only characters permitted in a URI
56
+ * by RFC 3986. Useful to prevent malformed endpoints being used to construct HTTP requests. */
57
+ static bool _endpoint_chars_are_valid(const char *endpoint_raw, mongocrypt_status_t *status) {
58
+ for (const char *c = endpoint_raw; *c != '\0'; c++) {
59
+ if (_is_unreserved(*c) || _is_reserved(*c)) {
60
+ continue;
61
+ }
62
+ if (*c == '%') {
63
+ /* Returns false if either character is the NUL terminator. */
64
+ if (!_is_hexdig(c[1])) {
65
+ CLIENT_ERR("Invalid percent-encoding in endpoint at offset %zu: 0x%02x",
66
+ (size_t)(c - endpoint_raw) + 1,
67
+ (unsigned char)c[1]);
68
+ return false;
69
+ }
70
+ if (!_is_hexdig(c[2])) {
71
+ CLIENT_ERR("Invalid percent-encoding in endpoint at offset %zu: 0x%02x",
72
+ (size_t)(c - endpoint_raw) + 2,
73
+ (unsigned char)c[2]);
74
+ return false;
75
+ }
76
+ c += 2;
77
+ continue;
78
+ }
79
+ CLIENT_ERR("Invalid character in endpoint at offset %zu: 0x%02x",
80
+ (size_t)(c - endpoint_raw),
81
+ (unsigned char)*c);
82
+ return false;
83
+ }
84
+ return true;
85
+ }
86
+
87
+ /* RFC 3986 section 2.2: sub-delims. Permitted in a reg-name; gen-delims are not. */
88
+ static bool _is_sub_delim(char c) {
89
+ return NULL != strchr("!$&'()*+,;=", c);
90
+ }
91
+
92
+ /* _host_chars_are_valid returns true if `host` is an RFC 3986 section 3.2.2 `reg-name`:
93
+ *
94
+ * reg-name = *( unreserved / pct-encoded / sub-delims )
95
+ *
96
+ * The gen-delims (":/?#[]@") are not permitted. The parse above already ends the host at ':', '/', and '?',
97
+ * so in practice this rejects '#', '@', and the brackets of an IP-literal.
98
+ *
99
+ * This is defense in depth. `host` reaches the Host header, where CR and LF are the only characters that can
100
+ * alter the message and kms-message rejects them, and `host_and_port` is passed to the consumer as the
101
+ * address to connect to, where a gen-delim yields a name that does not resolve rather than a structural
102
+ * problem. An `IP-literal` (RFC 3986 section 3.2.2) is not supported: the parse splits the host at the first
103
+ * colon with no bracket handling, so "[2001:db8::1]" yielded a host of "[2001" and a port of "db8::1]". */
104
+ static bool _host_chars_are_valid(const char *host, mongocrypt_status_t *status) {
105
+ for (const char *c = host; *c != '\0'; c++) {
106
+ if (_is_unreserved(*c) || _is_sub_delim(*c)) {
107
+ continue;
108
+ }
109
+ if (*c == '%') {
110
+ /* Already validated by _endpoint_chars_are_valid. */
111
+ c += 2;
112
+ continue;
113
+ }
114
+ CLIENT_ERR("Invalid character in endpoint host: 0x%02x", (unsigned char)*c);
115
+ return false;
116
+ }
117
+ return true;
118
+ }
119
+
120
+ /* _port_chars_are_valid returns true if `port` is a non-empty run of digits. */
121
+ static bool _port_chars_are_valid(const char *port, mongocrypt_status_t *status) {
122
+ if (*port == '\0') {
123
+ CLIENT_ERR("Invalid endpoint, expected a port after the colon");
124
+ return false;
125
+ }
126
+ for (const char *c = port; *c != '\0'; c++) {
127
+ if (*c < '0' || *c > '9') {
128
+ CLIENT_ERR("Invalid character in endpoint port: 0x%02x", (unsigned char)*c);
129
+ return false;
130
+ }
131
+ }
132
+ return true;
133
+ }
134
+
37
135
  /* Parses a subset of URIs of the form:
38
136
  * [protocol://][host[:port]][path][?query]
39
137
  */
@@ -61,6 +159,11 @@ _mongocrypt_endpoint_t *_mongocrypt_endpoint_new(const char *endpoint_raw,
61
159
  goto fail;
62
160
  }
63
161
 
162
+ /* Validate before parsing: every field parsed below is a substring of `original`. */
163
+ if (!_endpoint_chars_are_valid(endpoint->original, status)) {
164
+ goto fail;
165
+ }
166
+
64
167
  /* Parse optional protocol. */
65
168
  pos = strstr(endpoint->original, "://");
66
169
  if (pos) {
@@ -92,14 +195,15 @@ _mongocrypt_endpoint_t *_mongocrypt_endpoint_new(const char *endpoint_raw,
92
195
  colon = strstr(pos, ":");
93
196
  qmark = strstr(pos, "?");
94
197
  slash = strstr(pos, "/");
95
- if (colon) {
96
- host_end = colon;
97
- } else if (slash) {
198
+ /* The host ends at the first delimiter. A colon delimits a port only if it
199
+ * precedes the path and query: in "example.com/path:8080" the colon is part
200
+ * of the path, not a port separator. */
201
+ host_end = colon;
202
+ if (slash && (!host_end || slash < host_end)) {
98
203
  host_end = slash;
99
- } else if (qmark) {
204
+ }
205
+ if (qmark && (!host_end || qmark < host_end)) {
100
206
  host_end = qmark;
101
- } else {
102
- host_end = NULL;
103
207
  }
104
208
 
105
209
  if (host_end) {
@@ -112,12 +216,16 @@ _mongocrypt_endpoint_t *_mongocrypt_endpoint_new(const char *endpoint_raw,
112
216
  endpoint->host = bson_strdup(host_start);
113
217
  }
114
218
 
219
+ if (!_host_chars_are_valid(endpoint->host, status)) {
220
+ goto fail;
221
+ }
222
+
115
223
  /* Parse optional port */
116
- if (colon) {
224
+ if (colon && colon == host_end) {
117
225
  prev = colon + 1;
118
- qmark = strstr(pos, "?");
119
- slash = strstr(pos, "/");
120
- if (slash) {
226
+ qmark = strstr(prev, "?");
227
+ slash = strstr(prev, "/");
228
+ if (slash && (!qmark || slash < qmark)) {
121
229
  endpoint->port = bson_strndup(prev, (size_t)(slash - prev));
122
230
  } else if (qmark) {
123
231
  BSON_ASSERT(qmark >= prev);
@@ -125,6 +233,10 @@ _mongocrypt_endpoint_t *_mongocrypt_endpoint_new(const char *endpoint_raw,
125
233
  } else {
126
234
  endpoint->port = bson_strdup(prev);
127
235
  }
236
+
237
+ if (!_port_chars_are_valid(endpoint->port, status)) {
238
+ goto fail;
239
+ }
128
240
  }
129
241
 
130
242
  /* Parse optional path */