diff --git a/CHANGES.md b/CHANGES.md index 3e5e82c2..8a4f1f95 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,8 @@ Unreleased ### Bug fixes +* `get_string` and `get_wstring` now raise `ValueError` when the buffer is shorter than the declared current length, + instead of silently returning a truncated value (#945). * `get_wstring` compared the declared maximum in bytes with the 16382-character limit, so any WSTRING with a capacity of 8192 characters or more was rejected, including ones written by `set_wstring` (#923). * The get-block-info request now places the trailing `A` after the block number, as native Snap7 does diff --git a/snap7/util/getters.py b/snap7/util/getters.py index 26acae36..cb033624 100644 --- a/snap7/util/getters.py +++ b/snap7/util/getters.py @@ -218,6 +218,10 @@ def get_string(bytearray_: Buffer, byte_index: int, encoding: str = "latin-1") - "String contains {str_length} chars, but max. {max_string_size} chars are expected or is " "larger than 254. Bytearray doesn't seem to be a valid string." ) + if byte_index + 2 + str_length > len(bytearray_): + raise ValueError( + f"String contains {str_length} chars, but only {len(bytearray_) - byte_index - 2} bytes are in the buffer" + ) raw = bytes(bytearray_[byte_index + 2 : byte_index + 2 + str_length]) return raw.decode(encoding) @@ -795,4 +799,8 @@ def get_wstring(bytearray_: Buffer, byte_index: int) -> str: ) wstr_bytes = wstr_chars * 2 + if wstring_start + wstr_bytes > len(bytearray_): + raise ValueError( + f"WString contains {wstr_chars} chars, but only {len(bytearray_) - wstring_start} bytes are in the buffer" + ) return bytes(bytearray_[wstring_start : wstring_start + wstr_bytes]).decode("utf-16-be") diff --git a/tests/test_util.py b/tests/test_util.py index 5b0c592a..d194d8ec 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -8,7 +8,7 @@ from snap7 import DB, Row from snap7.type import Area, WordLen -from snap7.util import get_byte, get_time, get_fstring, get_int +from snap7.util import get_byte, get_time, get_fstring, get_int, get_string, get_wstring from snap7.util import set_byte, set_time, set_fstring, set_int from snap7.util.db import print_row @@ -1495,3 +1495,16 @@ def test_write_with_row_offset(self) -> None: if __name__ == "__main__": unittest.main() + + +@pytest.mark.parametrize( + "func,data", + [ + (get_string, bytearray([3, 3, 65])), + (get_string, bytearray([5, 4, 65, 66])), + (get_wstring, bytearray([0, 3, 0, 3, 0, 65, 0, 66])), + ], +) +def test_get_string_short_buffer_raises(func, data) -> None: # type: ignore[no-untyped-def] + with pytest.raises(ValueError): + func(data, 0)