diff options
author | Colin Snover | 2017-09-24 13:10:45 +0800 |
---|---|---|
committer | Colin Snover | 2017-10-29 13:18:37 -0500 |
commit | 37459bc6c17a69defc0d6d1f568dcd0261c78511 (patch) | |
tree | 97bcb791e55a75d9d3585332b377b887436b9b0c /engines/sci/engine | |
parent | 4233156505a73a2990aa4245cf2a3b356c88dc17 (diff) | |
download | scummvm-rg350-37459bc6c17a69defc0d6d1f568dcd0261c78511.tar.gz scummvm-rg350-37459bc6c17a69defc0d6d1f568dcd0261c78511.tar.bz2 scummvm-rg350-37459bc6c17a69defc0d6d1f568dcd0261c78511.zip |
SCI: Fix UB in SegManager memcpy/strcpy operations
Passing overlapping buffers to C standard library memcpy, strcpy,
and strncpy is undefined behavior. In SSCI these operations would
perform a forward copy, and most stdlib implementations do the
same, but at least newer Linux glibc on x86 copies bytes in
reverse, so just using the standard library on this platform
results in broken output.
Because SSCI used a blind forward copy instead of memmove for
overlapping copy operations, this patch implements an explicit
forward copy to ensure that overlapping copies continue to operate
the same as in SSCI.
This fixes the Island of Dr. Brain v1.1 flamingo puzzle
(script 185, flamingos::init, localCall 4c3) on platforms that do
not perform forward copy in memcpy/strcpy/strncpy.
Thanks to @moralrecordings for research on this bug and an initial
patch using memmove.
Closes gh-1034.
Diffstat (limited to 'engines/sci/engine')
-rw-r--r-- | engines/sci/engine/seg_manager.cpp | 30 |
1 files changed, 22 insertions, 8 deletions
diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp index aa70d5f838..45666336e9 100644 --- a/engines/sci/engine/seg_manager.cpp +++ b/engines/sci/engine/seg_manager.cpp @@ -614,7 +614,25 @@ static inline void setChar(const SegmentRef &ref, uint offset, byte value) { val->setOffset((val->getOffset() & 0xff00) | value); } -// TODO: memcpy, strcpy and strncpy could maybe be folded into a single function +template <bool STRING> +static void forwardCopy(byte *dest, const byte *src, size_t n) { + const bool zeroPad = (STRING && n != 0xFFFFFFFFU); + + while (n) { + --n; + const byte b = *src++; + *dest++ = b; + if (STRING && b == '\0') { + break; + } + } + if (zeroPad) { + while (n--) { + *dest++ = '\0'; + } + } +} + void SegManager::strncpy(reg_t dest, const char* src, size_t n) { SegmentRef dest_r = dereference(dest); if (!dest_r.isValid()) { @@ -624,11 +642,7 @@ void SegManager::strncpy(reg_t dest, const char* src, size_t n) { if (dest_r.isRaw) { - // raw -> raw - if (n == 0xFFFFFFFFU) - ::strcpy((char *)dest_r.raw, src); - else - ::strncpy((char *)dest_r.raw, src, n); + forwardCopy<true>(dest_r.raw, (const byte *)src, n); } else { // raw -> non-raw for (uint i = 0; i < n; i++) { @@ -711,7 +725,7 @@ void SegManager::memcpy(reg_t dest, const byte* src, size_t n) { if (dest_r.isRaw) { // raw -> raw - ::memcpy((char *)dest_r.raw, src, n); + forwardCopy<false>(dest_r.raw, src, n); } else { // raw -> non-raw for (uint i = 0; i < n; i++) @@ -767,7 +781,7 @@ void SegManager::memcpy(byte *dest, reg_t src, size_t n) { if (src_r.isRaw) { // raw -> raw - ::memcpy(dest, src_r.raw, n); + forwardCopy<false>(dest, src_r.raw, n); } else { // non-raw -> raw for (uint i = 0; i < n; i++) { |