Repository: trailofbits/polytracker (language: python)

Issue:
Add support for tracking writes to sockets, similar to how we handle STDOUT



Discussion / hints:

Fix patch:
diff --git a/polytracker/custom_abi/dfsan_abilist.txt b/polytracker/custom_abi/dfsan_abilist.txt
index 018931e8..1fcdcef9 100644
--- a/polytracker/custom_abi/dfsan_abilist.txt
+++ b/polytracker/custom_abi/dfsan_abilist.txt
@@ -221,7 +221,7 @@ fun:sem_destroy=discard
 fun:sem_init=discard
 fun:sem_post=discard
 fun:sem_wait=discard
-fun:send=discard
+# fun:send=discard
 fun:sendmsg=discard
 fun:sendto=discard
 fun:setsockopt=discard
@@ -3229,6 +3229,7 @@ fun:semget=uninstrumented
 fun:semop=uninstrumented
 fun:semtimedop=uninstrumented
 fun:send=uninstrumented
+fun:send=custom
 fun:sendfile=uninstrumented
 fun:sendfile64=uninstrumented
 fun:sendmmsg=uninstrumented
diff --git a/polytracker/include/polytracker/early_construct.h b/polytracker/include/polytracker/early_construct.h
index 41ef9171..5a81c967 100644
--- a/polytracker/include/polytracker/early_construct.h
+++ b/polytracker/include/polytracker/early_construct.h
@@ -22,7 +22,7 @@ get(typename std::aligned_storage<sizeof(T), alignof(T)>::type &src) {
 template <typename T, typename... Args>
 __attribute__((always_inline)) void
 construct(typename std::aligned_storage<sizeof(T), alignof(T)>::type &dst,
-          Args &&... args) {
+          Args &&...args) {
   ::new (&dst) T(std::forward<Args>(args)...);
 }
 
diff --git a/polytracker/include/taintdag/error.h b/polytracker/include/taintdag/error.h
index 2dbdd4dd..bdfd5a1e 100644
--- a/polytracker/include/taintdag/error.h
+++ b/polytracker/include/taintdag/error.h
@@ -17,7 +17,7 @@ namespace taintdag {
 
 extern std::function<void(int)> error_function;
 
-template <typename... Msgs> void error_exit(Msgs &&... msgs) {
+template <typename... Msgs> void error_exit(Msgs &&...msgs) {
   std::stringstream ss;
   (ss << ... << msgs);
   spdlog::error(ss.str());
diff --git a/polytracker/include/taintdag/outputfile.h b/polytracker/include/taintdag/outputfile.h
index ebb8c122..2782036b 100644
--- a/polytracker/include/taintdag/outputfile.h
+++ b/polytracker/include/taintdag/outputfile.h
@@ -19,22 +19,19 @@
 namespace taintdag {
 
 // Records requirements on a Section in the OutputFile
-template <typename T> concept Section = requires(T a) {
+template <typename T>
+concept Section = requires(T a) {
   // How much memory should be reserved for this seciton in the OutputFile.
-  { T::allocation_size }
-  ->std::convertible_to<std::size_t>;
+  { T::allocation_size } -> std::convertible_to<std::size_t>;
 
   // Alignment requirements on the section
-  { T::align_of }
-  ->std::convertible_to<std::size_t>;
+  { T::align_of } -> std::convertible_to<std::size_t>;
 
   // A type tag for this section
-  { T::tag }
-  ->std::convertible_to<std::uint8_t>;
+  { T::tag } -> std::convertible_to<std::uint8_t>;
 
   // The actual amount of memory currently used
-  { a.size() }
-  ->std::convertible_to<std::size_t>;
+  { a.size() } -> std::convertible_to<std::size_t>;
 };
 
 // Need a single arg to create instances of the Section type in place
diff --git a/polytracker/include/taintdag/polytracker.h b/polytracker/include/taintdag/polytracker.h
index ab55f426..08211fef 100644
--- a/polytracker/include/taintdag/polytracker.h
+++ b/polytracker/include/taintdag/polytracker.h
@@ -55,9 +55,9 @@ class PolyTracker {
   void affects_control_flow(label_t taint_label);
 
   // Log tainted data flowed into the sink
-  void taint_sink(int fd, sink_offset_t offset, void const *mem, size_t length);
+  void taint_sink(int fd, util::Offset offset, void const *mem, size_t length);
   // Same as before, but use same label for all data
-  void taint_sink(int fd, sink_offset_t offset, label_t label, size_t length);
+  void taint_sink(int fd, util::Offset offset, label_t label, size_t length);
 
   // Log function entry
   Functions::index_t function_entry(std::string_view name);
@@ -83,9 +83,10 @@ class PolyTracker {
   ConcreteOutputFile output_file_;
 
   // Tracking source offsets for streams (where offsets can be determined by
-  // ftell) per source index.
+  // ftell) per source/sink index.
   static constexpr size_t offset_capacity = size_t{max_source_index} + 1;
-  StreamOffset<offset_capacity> stream_offsets_;
+  StreamOffset<offset_capacity> stream_read_offsets_;
+  StreamOffset<offset_capacity> stream_write_offsets_;
 };
 
 } // namespace taintdag
diff --git a/polytracker/include/taintdag/section.h b/polytracker/include/taintdag/section.h
index 0095cca8..cc699007 100644
--- a/polytracker/include/taintdag/section.h
+++ b/polytracker/include/taintdag/section.h
@@ -136,7 +136,7 @@ template <typename T> struct FixedSizeAlloc : SectionBase {
   // access to the section for the life time of the ConstructCtx.
   // N.b. count()/size() competes for the same lock.
   template <typename... Args>
-  std::optional<ConstructCtx> construct(Args &&... args) {
+  std::optional<ConstructCtx> construct(Args &&...args) {
     if (auto write_context = SectionBase::write(entry_size())) {
       return ConstructCtx{.ctx = std::move(*write_context),
                           .t = *new (&*(write_context->mem.begin()))
diff --git a/polytracker/include/taintdag/stream_offset.h b/polytracker/include/taintdag/stream_offset.h
index cfd17585..f05dbcc0 100644
--- a/polytracker/include/taintdag/stream_offset.h
+++ b/polytracker/include/taintdag/stream_offset.h
@@ -26,10 +26,11 @@ template <size_t SourceCount> class StreamOffset {
   // concurrent read operations for the same source is the result of invalid
   // code. Reads on the same source should be protected from running in
   // parallell (at least for the taint sources considered here, e.g. files).
-  source_offset_t read(source_index_t idx, size_t len) {
+  source_offset_t increase(source_index_t idx, size_t len) {
     if (idx >= SourceCount) {
-      error_exit("Attemted read of source index ", static_cast<uint64_t>(idx),
-                 ", only ", SourceCount, " sources available");
+      error_exit("Attempted increase offset of source index ",
+                 static_cast<uint64_t>(idx), ", only ", SourceCount,
+                 " sources available");
     }
     return offset[idx].fetch_add(len);
   }
diff --git a/polytracker/src/taint_sources/write_taints.cpp b/polytracker/src/taint_sources/write_taints.cpp
index f77ba409..575cdb95 100644
--- a/polytracker/src/taint_sources/write_taints.cpp
+++ b/polytracker/src/taint_sources/write_taints.cpp
@@ -4,6 +4,7 @@
 #include "taintdag/polytracker.h"
 #include <iostream>
 #include <sanitizer/dfsan_interface.h>
+#include <sys/socket.h>
 #include <sys/types.h>
 #include <unistd.h>
 
@@ -21,17 +22,46 @@ ShadowMask() = static const uptr kShadowMask = ~0x700000000000;
 */
 EARLY_CONSTRUCT_EXTERN_GETTER(taintdag::PolyTracker, polytracker_tdag);
 
+namespace {
+
+// Records the taint operation on fd
+//
+// Will only record writes where `length.valid()`. That is when at least one
+// byte was written.
+// `fd` is the file descriptor that was written to
+// `offset` is the offset writes started from
+// `length` is the amount of data written
+// `buffer` is the source buffer written to `fd`
+void impl_taint_sink(int fd, util::Offset offset, util::Length length,
+                     void *buffer) {
+  if (length.valid()) {
+    get_polytracker_tdag().taint_sink(fd, offset, buffer, *length.value());
+  }
+}
+
+// Implementation wrapper for write/send-style functions
+//
+// `ret_label` is the return value label, will be cleared
+// `fd` file descriptor to use
+// `buffer` is the buffer written from
+// `write_func` is the function that performs the operation (e.g. `write`)
+// `write_func_args` are the arguments to pass to `write_func`
+template <typename F, typename... Args>
+ssize_t impl_write_send(dfsan_label &ret_label, int fd, void *buffer,
+                        F &&write_func, Args... write_func_args) {
+  auto offset = util::Offset::from_fd(fd);
+  auto retval = write_func(write_func_args...);
+  impl_taint_sink(fd, offset, util::Length::from_returned_size(retval), buffer);
+  ret_label = 0;
+  return retval;
+}
+} // namespace
+
 EXT_C_FUNC ssize_t __dfsw_write(int fd, void *buf, size_t count,
                                 dfsan_label fd_label, dfsan_label buff_label,
                                 dfsan_label count_label,
                                 dfsan_label *ret_label) {
-  auto current_offset = lseek(fd, 0, SEEK_CUR);
-  auto write_count = write(fd, buf, count);
-  if (write_count > 0) {
-    get_polytracker_tdag().taint_sink(fd, current_offset, buf, write_count);
-  }
-  *ret_label = 0;
-  return write_count;
+  return impl_write_send(*ret_label, fd, buf, write, fd, buf, count);
 }
 
 EXT_C_FUNC size_t __dfsw_fwrite(void *buf, size_t size, size_t count,
@@ -39,24 +69,21 @@ EXT_C_FUNC size_t __dfsw_fwrite(void *buf, size_t size, size_t count,
                                 dfsan_label size_label, dfsan_label count_label,
                                 dfsan_label stream_label,
                                 dfsan_label *ret_label) {
-  auto current_offset = ftell(stream);
+  auto offset = util::Offset::from_file(stream);
   auto write_count = fwrite(buf, size, count, stream);
-  auto fd = fileno(stream);
-  if (write_count > 0) {
-    get_polytracker_tdag().taint_sink(fd, current_offset, buf,
-                                      write_count * size);
-  }
+  auto length = util::Length::from_returned_size_count(size, write_count);
+  impl_taint_sink(fileno(stream), offset, length, buf);
   *ret_label = 0;
   return write_count;
 }
 
 EXT_C_FUNC int __dfsw_putc(int ch, FILE *stream, dfsan_label ch_label,
                            dfsan_label stream_label, dfsan_label *ret_label) {
-  auto offset = ftell(stream);
+  auto offset = util::Offset::from_file(stream);
   auto ret = fputc(ch, stream);
   if (ret == ch) {
-    auto fd = fileno(stream);
-    get_polytracker_tdag().taint_sink(fd, offset, ch_label, sizeof(char));
+    get_polytracker_tdag().taint_sink(fileno(stream), offset, ch_label,
+                                      sizeof(char));
   }
   *ret_label = 0;
   return ret;
@@ -66,3 +93,15 @@ EXT_C_FUNC int __dfsw_fputc(int ch, FILE *stream, dfsan_label ch_label,
                             dfsan_label stream_label, dfsan_label *ret_label) {
   return __dfsw_putc(ch, stream, ch_label, stream_label, ret_label);
 }
+
+// Socket functions
+EXT_C_FUNC ssize_t __dfsw_send(int socket, void *buffer, size_t length,
+                               int flags, dfsan_label socket_label,
+                               dfsan_label buffer_label,
+                               dfsan_label length_label,
+                               dfsan_label flags_label,
+                               dfsan_label *ret_label) {
+
+  return impl_write_send(*ret_label, socket, buffer, send, socket, buffer,
+                         length, flags);
+}
diff --git a/polytracker/src/taintdag/polytracker.cpp b/polytracker/src/taintdag/polytracker.cpp
index 7ef72d86..89f41bad 100644
--- a/polytracker/src/taintdag/polytracker.cpp
+++ b/polytracker/src/taintdag/polytracker.cpp
@@ -77,9 +77,9 @@ std::optional<taint_range_t> PolyTracker::source_taint(int fd, void const *mem,
                                                        util::Offset offset,
                                                        size_t length) {
   if (auto source_index = output_file_.section<Sources>().mapping_idx(fd)) {
-    auto offset_value = offset.valid()
-                            ? *offset.value()
-                            : stream_offsets_.read(*source_index, length);
+    auto offset_value =
+        offset.valid() ? *offset.value()
+                       : stream_read_offsets_.increase(*source_index, length);
     return create_source_taint(*source_index,
                                {reinterpret_cast<uint8_t const *>(mem), length},
                                offset_value);
@@ -95,9 +95,9 @@ std::optional<taint_range_t> PolyTracker::source_taint(int fd, void const *mem,
 std::optional<taint_range_t>
 PolyTracker::source_taint(int fd, util::Offset offset, size_t length) {
   if (auto source_index = output_file_.section<Sources>().mapping_idx(fd)) {
-    auto offset_value = offset.valid()
-                            ? *offset.value()
-                            : stream_offsets_.read(*source_index, length);
+    auto offset_value =
+        offset.valid() ? *offset.value()
+                       : stream_read_offsets_.increase(*source_index, length);
     auto range = output_file_.section<Labels>().create_source_labels(
         *source_index, offset_value, length);
     output_file_.section<SourceLabelIndexSection>().set_range(
@@ -122,7 +122,7 @@ PolyTracker::create_taint_source(std::string_view name,
   return {};
 }
 
-void PolyTracker::taint_sink(int fd, sink_offset_t offset, void const *mem,
+void PolyTracker::taint_sink(int fd, util::Offset offset, void const *mem,
                              size_t length) {
 
   // TODO (hbrodin): Optimize this. Add a way of representing the entire write
@@ -140,23 +140,33 @@ void PolyTracker::taint_sink(int fd, sink_offset_t offset, void const *mem,
   if (auto idx = output_file_.section<Sources>().mapping_idx(fd)) {
     std::span<uint8_t const> src{reinterpret_cast<uint8_t const *>(mem),
                                  length};
+    auto offset_value = offset.valid()
+                            ? *offset.value()
+                            : stream_write_offsets_.increase(*idx, length);
     for (auto &c : src) {
       auto lbl = dfsan_read_label(&c, sizeof(char));
       if (lbl > 0)
-        output_file_.section<TaintSink>().log_single(offset, lbl, *idx);
-      ++offset;
+        output_file_.section<TaintSink>().log_single(offset_value, lbl, *idx);
+      ++offset_value;
     }
   }
 }
 
-void PolyTracker::taint_sink(int fd, sink_offset_t offset, label_t label,
+void PolyTracker::taint_sink(int fd, util::Offset offset, label_t label,
                              size_t length) {
-  if (label == 0)
-    return;
 
   if (auto idx = output_file_.section<Sources>().mapping_idx(fd)) {
+
+    auto offset_value = offset.valid()
+                            ? *offset.value()
+                            : stream_write_offsets_.increase(*idx, length);
+
+    if (label == 0) {
+      return;
+    }
     for (size_t i = 0; i < length; ++i) {
-      output_file_.section<TaintSink>().log_single(offset + i, label, *idx);
+      output_file_.section<TaintSink>().log_single(offset_value + i, label,
+                                                   *idx);
     }
   }
 }


Test patch:
diff --git a/tests/test_socket_read.cpp b/tests/test_socket_read_write.cpp
similarity index 75%
rename from tests/test_socket_read.cpp
rename to tests/test_socket_read_write.cpp
index 709964e8..8a0b28a6 100644
--- a/tests/test_socket_read.cpp
+++ b/tests/test_socket_read_write.cpp
@@ -8,29 +8,32 @@
 #include <unistd.h>
 
 
-int read_source_data(int conn, std::string_view expected_data) {
+int read_write_connection_data(int conn) {
 
-  int ret;
-  char buffer[7]{0};
+  ssize_t ret;
+  char buffer[6];
 
-  // Receive source taint via read
+  // Receive source taint via read, recv and recvfrom
   ret = read(conn, buffer, 2);
   assert(ret == 2);
 
-  // Receive source taint via recv
   ret = recv(conn, &buffer[2], 2, 0);
   assert(ret == 2);
 
-  // Receive source taint via recvfrom
   ret = recvfrom(conn, &buffer[4], 2, 0, nullptr, nullptr);
   assert(ret == 2);
-  // Ensure we received the correct data
-  assert(expected_data == buffer);
+
+  // Split the echo reply across write and send
+  ret = write(conn, &buffer[0], 3);
+  assert(ret == 3);
+
+  ret = send(conn, &buffer[3], 3, 0);
+  assert(ret == 3);
 
   return 0;
 }
 
-int client(uint16_t port, std::string_view expected_data) {
+int client(uint16_t port) {
   int ret;
   auto s = socket(PF_INET, SOCK_STREAM, 0);
   assert(s >= 0);
@@ -42,7 +45,7 @@ int client(uint16_t port, std::string_view expected_data) {
   ret = connect(s, reinterpret_cast<sockaddr const *>(&server_address), sizeof(server_address));
   assert(ret == 0);
 
-  ret = read_source_data(s, expected_data);
+  ret = read_write_connection_data(s);
   assert(ret == 0);
 
   ret = shutdown(s, SHUT_RDWR);
@@ -53,7 +56,7 @@ int client(uint16_t port, std::string_view expected_data) {
 }
 
 
-int server(uint16_t port, std::string_view expected_data) {
+int server(uint16_t port) {
   int ret;
   auto s = socket(PF_INET, SOCK_STREAM, 0);
   assert(s >= 0);
@@ -63,7 +66,7 @@ int server(uint16_t port, std::string_view expected_data) {
   assert(ret == 0);
 
   const int disable = 0;
-  struct linger linger {.l_linger = 0, .l_onoff = 0};
+  struct linger linger {.l_onoff = 0, .l_linger = 0};
   ret = setsockopt(s, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
   assert(ret == 0);
 
@@ -80,7 +83,7 @@ int server(uint16_t port, std::string_view expected_data) {
   int client_socket = accept(s, nullptr, nullptr); // Don't care about client address
   assert(client_socket >= 0);
 
-  ret = read_source_data(client_socket, expected_data);
+  ret = read_write_connection_data(client_socket);
   assert(ret == 0);
 
   // Close the client connection
@@ -105,12 +108,10 @@ int main(int argc, char* argv[]) {
   auto port = std::stoul(argv[2]);
   assert(port <= std::numeric_limits<uint16_t>::max());
 
-  std::string_view expected_data = argv[3];
-
   if (mode == "client") {
-    return client(static_cast<uint16_t>(port), expected_data);
+    return client(static_cast<uint16_t>(port));
   } else if (mode == "server") {
-    return server(static_cast<uint16_t>(port), expected_data);
+    return server(static_cast<uint16_t>(port));
   } else {
     return -1;
   }
diff --git a/tests/test_socket_read.py b/tests/test_socket_read_write.py
similarity index 61%
rename from tests/test_socket_read.py
rename to tests/test_socket_read_write.py
index ffd8439a..bf88346a 100644
--- a/tests/test_socket_read.py
+++ b/tests/test_socket_read_write.py
@@ -8,7 +8,7 @@
 from pathlib import Path
 
 # The maximum duration for the test case (to prevent hangs)
-MAX_DURATION_SECS = 10
+MAX_DURATION_SECS = 10.0
 
 
 def expected_source_name(binary_ip, binary_port, python_ip, python_port):
@@ -16,9 +16,9 @@ def expected_source_name(binary_ip, binary_port, python_ip, python_port):
     return f"socket:{binary_ip}:{binary_port}-{python_ip}:{python_port}"
 
 
-@pytest.mark.program_trace("test_socket_read.cpp")
+@pytest.mark.program_trace("test_socket_read_write.cpp")
 @pytest.mark.parametrize("mode", ["client", "server"])
-def test_socket_read(instrumented_binary: Path, trace_file: Path, mode: str):
+def test_socket_read_write(instrumented_binary: Path, trace_file: Path, mode: str):
     """Tests reads from the socket, introducing source taint.
 
     The mode is seen from the instrumented binary point of view. That is, client
@@ -27,6 +27,12 @@ def test_socket_read(instrumented_binary: Path, trace_file: Path, mode: str):
     """
     start = time.time()
 
+    def abort_on_timeout():
+        """Aborts if the timeout has passed, returns remaining time if not"""
+        elapsed = time.time() - start
+        assert elapsed < MAX_DURATION_SECS
+        return MAX_DURATION_SECS - elapsed
+
     # Data to write to stdin, one byte at a time
     send_data = "abcdef".encode("utf-8")
     port_number = 63455
@@ -38,6 +44,16 @@ def start_proc():
             env={"POLYDB": str(trace_file)},
         )
 
+    def read_n(sock, n):
+        """Read n bytes from the socket, abort on test case timeout"""
+        remaining_bytes = n
+        response = bytearray()
+        while remaining_bytes > 0:
+            abort_on_timeout()
+            response += sock.recv(remaining_bytes)
+            remaining_bytes = n - len(response)
+        return response
+
     with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
         s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 
@@ -49,6 +65,7 @@ def start_proc():
 
             conn, addr = s.accept()
             conn.sendall(send_data)
+            response = read_n(conn, len(send_data))
             conn.shutdown(socket.SHUT_RDWR)
 
             source_name = expected_source_name(addr[0], addr[1], host, port_number)
@@ -60,8 +77,7 @@ def start_proc():
             # Retry connections until timeout
             while True:
                 try:
-                    elapsed = int(time.time() - start)
-                    assert elapsed < MAX_DURATION_SECS
+                    abort_on_timeout()
                     s.connect((host, port_number))
                     break
                 except ConnectionRefusedError:
@@ -72,22 +88,36 @@ def start_proc():
                 host, port_number, client_host, client_port
             )
             s.sendall(send_data)
+
+            # Read response, might come in chunks
+            response = read_n(s, len(send_data))
+
             s.shutdown(socket.SHUT_RDWR)
 
     # Wait for process to terminate, should be quick unless error
-    elapsed = int(time.time() - start)
-    if elapsed > MAX_DURATION_SECS:
-        wait_time = 0
-    else:
-        wait_time = MAX_DURATION_SECS - elapsed
-    proc.wait(wait_time)
+    remaining_time = abort_on_timeout()
+    proc.wait(remaining_time)
+
+    # Ensure actual program logic worked out ok
     assert proc.returncode == 0
+    assert send_data == response
 
+    # Check taint operations
     program_trace = polytracker.PolyTrackerTrace.load(trace_file)
     input_labels = list(program_trace.tdfile.input_labels())
     inputs = list(program_trace.inputs)
+    sinkops = list(program_trace.tdfile.sinks)
 
+    # Ensure socket reads are accounted for
     assert len(input_labels) == len(send_data)
 
     assert len(inputs) == 1
     assert inputs[0].path == source_name
+
+    # Ensure socket writes are accounted for
+    assert len(sinkops) == len(send_data)
+    for i in range(0, len(send_data)):
+        assert inputs[sinkops[i].fdidx].path == source_name
+        assert sinkops[i].offset == i
+        # As label zero is unused the labels are offset by one
+        assert sinkops[i].label == i + 1
diff --git a/tests/test_stdio.py b/tests/test_stdio.py
index af6f0a75..335dbf03 100644
--- a/tests/test_stdio.py
+++ b/tests/test_stdio.py
@@ -24,3 +24,8 @@ def test_stdio(set_env_vars, program_trace: ProgramTrace):
     assert len(sinks) == 2
     assert paths[sinks[0].fdidx] == stdout
     assert paths[sinks[1].fdidx] == stderr
+
+    # Lots of output is written before this write
+    # NOTE(hbrodin): This is a bit fragile as it depends on how much debug output is written
+    assert sinks[0].offset > 0
+    assert sinks[1].offset > 0
diff --git a/unittests/src/taintdag/stream_offset.cpp b/unittests/src/taintdag/stream_offset.cpp
index b39bbc51..95e5f78b 100644
--- a/unittests/src/taintdag/stream_offset.cpp
+++ b/unittests/src/taintdag/stream_offset.cpp
@@ -9,25 +9,25 @@ namespace taintdag {
 TEST_CASE("StreamOffset", "StreamOffset") {
 
   StreamOffset<4> ofs;
-  REQUIRE(ofs.read(0, 0) == 0);
-  REQUIRE(ofs.read(0, 0) == 0);
+  REQUIRE(ofs.increase(0, 0) == 0);
+  REQUIRE(ofs.increase(0, 0) == 0);
 
   SECTION("Reading 3 bytes twice") {
-    REQUIRE(ofs.read(0, 3) == 0);
-    REQUIRE(ofs.read(0, 3) == 3);
+    REQUIRE(ofs.increase(0, 3) == 0);
+    REQUIRE(ofs.increase(0, 3) == 3);
   }
 
   SECTION("Reads doesn't interfer") {
-    ofs.read(0, 99);
-    ofs.read(1, 2);
+    ofs.increase(0, 99);
+    ofs.increase(1, 2);
 
-    REQUIRE(ofs.read(0, 1) == 99);
-    REQUIRE(ofs.read(1, 1) == 2);
+    REQUIRE(ofs.increase(0, 1) == 99);
+    REQUIRE(ofs.increase(1, 1) == 2);
   }
 
   SECTION("SourceIndex out of bounds aborts") {
     test::ErrorExitReplace errthrow;
-    REQUIRE_THROWS_AS(ofs.read(4, 1), test::ErrorExit);
+    REQUIRE_THROWS_AS(ofs.increase(4, 1), test::ErrorExit);
   }
 }