Vulnerability: Potentially dangerous out-of-bounds memory access in `BytesParsing::sliceUnchecked`
Severity: low

Description:
`BytesParsing::sliceUnchecked` currently bails early for the degenerate case when the slice `length` is zero; however, there is no validation on the `length` of the `encoded` bytes parameter `encoded` itself. If the `length` of `encoded` is less than the slice `length`, then it is possible to access memory out-of-bounds.\\n```\\nfunction sliceUnchecked(bytes memory encoded, uint256 offset, uint256 length)\\n    internal\\n    pure\\n    returns (bytes memory ret, uint256 nextOffset)\\n{\\n    //bail early for degenerate case\\n    if (length == 0) {\\n        return (new bytes(0), offset);\\n    }\\n\\n    assembly ("memory-safe") {\\n        nextOffset := add(offset, length)\\n        ret := mload(freeMemoryPtr)\\n\\n        /* snip: inline dev comments */\\n\\n        let shift := and(length, 31) //equivalent to `mod(length, 32)` but 2 gas cheaper\\n        if iszero(shift) { shift := wordSize }\\n\\n        let dest := add(ret, shift)\\n        let end := add(dest, length)\\n        for { let src := add(add(encoded, shift), offset) } lt(dest, end) {\\n            src := add(src, wordSize)\\n            dest := add(dest, wordSize)\\n        } { mstore(dest, mload(src)) }\\n\\n        mstore(ret, length)\\n        //When compiling with --via-ir then normally allocated memory (i.e. via new) will have 32 byte\\n        //  memory alignment and so we enforce the same memory alignment here.\\n        mstore(freeMemoryPtr, and(add(dest, 31), not(31)))\\n    }\\n}\\n```\\n\\nSince the `for` loop begins at the offset of `encoded` in memory, accounting `for` its `length` and accompanying `shift` calculation depending on the `length` supplied, and execution continues so long as `dest` is less than `end`, it is possible to continue loading additional words out of bounds simply by passing larger `length` values. Therefore, regardless of the `length` of the original bytes, the output slice will always have a size defined by the `length` parameter.\\nIt is understood that this is known behavior due to the unchecked nature of this function and the accompanying checked version, which performs validation on the `nextOffset` return value compared with the length of the encoded bytes.\\n```\\nfunction slice(bytes memory encoded, uint256 offset, uint256 length)\\n    internal\\n    pure\\n    returns (bytes memory ret, uint256 nextOffset)\\n{\\n    (ret, nextOffset) = sliceUnchecked(encoded, offset, length);\\n    checkBound(nextOffset, encoded.length);\\n}\\n```\\n\\nIt has not been possible within the constraints of this review to identify a valid scenario in which malicious calldata can make use of this behavior to launch a successful exploit; however, this is not a guarantee that the usage of this library function is bug-free since there do exist certain quirks related to the loading of calldata.

Impact:
The impact is limited in the context of the library function's usage in the scope of this review; however, it is advisable to check any other usage elsewhere and in the future to ensure that this behavior cannot be weaponized. `BytesParsing::sliceUnchecked` is currently only used in `WormholeCctpMessages::_decodeBytes`, which itself is called in `WormholeCctpMessages::decodeDeposit`. This latter function is utilized in two places:

Recommended mitigation:
Consider bailing early if the length of the bytes from which to construct a slice is zero, and always ensure the resultant offset is correctly validated against the length when using the unchecked version of the function.