Skip to content

fix: CBOR subscript force unwrap that crashes on nil assignment#126

Open
hamchapman wants to merge 5 commits into
hc/fix-typo-in-shouldsortmapkeys-optionfrom
hc/fix-cbor-subscript-unwrap
Open

fix: CBOR subscript force unwrap that crashes on nil assignment#126
hamchapman wants to merge 5 commits into
hc/fix-typo-in-shouldsortmapkeys-optionfrom
hc/fix-cbor-subscript-unwrap

Conversation

@hamchapman

Copy link
Copy Markdown
Collaborator

The subscript setter had force unwraps that would crash when setting a value to nil.

Now we don't force unwrap and when setting an array's value by index to nil the element will be updated to become a CBOR.null.

These changes address multiple critical safety issues:

**Issue #118 - Large allocation crash:**
- Replace `.map()` with progressive array building in `readN()`
- Prevents memory allocation crashes from malformed CBOR with huge declared lengths
- Add capacity reservation capped at reasonable size (1024)

**Issue #69 - Array bounds checking:**
- Add bounds validation before accessing data ranges in `UnkeyedDecodingContainer`
- Prevents crashes from corrupted data with out-of-bounds ranges
- Throw proper `DecodingError` instead of crashing

**Unsafe `try!` replacements:**
- AnyCodingKey: Replace `try!` with proper error handling for invalid key types
- KeyedDecodingContainer: Replace `try!` with proper error propagation
- KeyedDecodingContainer: Replace `fatalError()` with throwing DecodingError
- UnkeyedDecodingContainer: Remove `fatalError()`, allow errors to propagate
- AnyCodingKey.encode(): Replace `fatalError()` with throwing EncodingError
- AnyCodingKey.key(): Replace `fatalError()` with preconditionFailure (better messages)

**Encoder changes:**
- Make `encodeMap()` and `encodeMapChunk()` throw instead of using `try!`
- Update call sites with proper error handling
- Add `preconditionFailure` for programming errors (`forbidNonStringMapKeys` violations)
Previously, `superEncoder()` and `superEncoder(forKey:)` methods in both
`KeyedEncodingContainer` and `UnkeyedEncodingContainer` would crash with
`fatalError("Unimplemented")`.

This commit implements these methods to properly support Swift class inheritance
with Codable, allowing subclasses to encode their superclass properties into
nested containers.

**Changes:**

- `KeyedEncodingContainer.superEncoder()`: Creates encoder under "super" key
- `KeyedEncodingContainer.superEncoder(forKey:)`: Creates encoder under custom key
- `UnkeyedEncodingContainer.superEncoder()`: Appends encoder as array element
- Make `_CBOREncoder` conform to `CBOREncodingContainer` protocol
This change includes 2 main changes.

Previously, the `maximumDepth` option was defined but not actually used in
`CodableCBORDecoder`, creating a DoS vulnerability where deeply nested
structures could cause stack overflow.

**Problem:**

1. `CodableCBORDecoder` had a `maximumDepth` property but it wasn't included
   in the options computed property, so it defaulted to `.max`
2. `SingleValueDecodingContainer` called `CBOR.decode()` without passing options
3. `KeyedDecodingContainer` and `UnkeyedDecodingContainer` created `CBORDecoder`
   instances without passing options
4. This meant depth checking was never enforced for Codable decoding

**Fix:**

- Add `maximumDepth` property to `CodableCBORDecoder` (public API)
- Include `maximumDepth` in options computed property
- Update `setOptions()` to set `maximumDepth`
- Pass options to all `CBOR.decode()` calls in `SingleValueDecodingContainer`
- Pass options to `CBORDecoder()` calls in `KeyedDecodingContainer`
- Pass options to `CBORDecoder()` calls in `UnkeyedDecodingContainer`

Changes:
- Added `currentDepth` field to `_CBORDecoder` and all container classes
  (`KeyedContainer`, `UnkeyedContainer`, `SingleValueContainer`)
- Thread `currentDepth` through all container creation and nested decoding
- Check depth before creating keyed/unkeyed containers in `_CBORDecoder`
- Increment depth when creating nested decoders in `decode()` methods
- Pass incremented depth through `superDecoder()` methods

Previously, each `CBOR.decode()` call would reset `currentDepth` to 0, allowing
deeply nested structures to bypass the depth limit. For example, a 10-level
nested array with `maximumDepth=5` would incorrectly succeed. Now depth is
properly tracked across the entire container hierarchy.
…ortMapKeys`

The parameter name in `CBOROptions.init()` was incorrectly spelled as
`shouldShortMapKeys` when it should be `shouldSortMapKeys` to match the property
name and convey the correct meaning (sorting keys, not shortening them).
The subscript setter had force unwraps that would crash when setting a value to
`nil`.

Now we don't force unwrap and when setting an array's value by index to `nil`
the element will be updated to become a `CBOR.null`.
@hamchapman hamchapman self-assigned this Feb 2, 2026
@hamchapman hamchapman force-pushed the hc/fix-typo-in-shouldsortmapkeys-option branch from e4cbb36 to d572f48 Compare February 2, 2026 23:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant