From c86ddbbc0eee0e8317d4ed180ee4505f10e8a52c Mon Sep 17 00:00:00 2001
From: Jari Vetoniemi <mailroxas@gmail.com>
Date: Thu, 18 Oct 2018 13:25:53 +0300
Subject: Initial commit

---
 Makefile         |  28 ++++++++
 proc-region-rw.c | 191 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 219 insertions(+)
 create mode 100644 Makefile
 create mode 100644 proc-region-rw.c

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4086d15
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,28 @@
+PREFIX ?= /usr/local
+bindir ?= /bin
+
+MAKEFLAGS += --no-builtin-rules
+
+WARNINGS := -Wall -Wextra -Wpedantic -Wformat=2 -Wstrict-aliasing=3 -Wstrict-overflow=5 -Wstack-usage=12500 \
+	-Wfloat-equal -Wcast-align -Wpointer-arith -Wchar-subscripts -Warray-bounds=2
+
+override CFLAGS ?= -g
+override CFLAGS += -std=c99 -D_DEFAULT_SOURCE $(WARNINGS)
+
+bins = proc-region-rw
+all: $(bins)
+
+$(bins): %:
+	$(LINK.c) $(filter %.c %.a,$^) $(LDLIBS) -o $@
+
+proc-region-rw: proc-region-rw.c
+
+install-bin: $(bins)
+	install -Dm755 $^ -t "$(DESTDIR)$(PREFIX)$(bindir)"
+
+install: install-bin
+
+clean:
+	$(RM) $(bins)
+
+.PHONY: all clean install
diff --git a/proc-region-rw.c b/proc-region-rw.c
new file mode 100644
index 0000000..0c1ca4f
--- /dev/null
+++ b/proc-region-rw.c
@@ -0,0 +1,191 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#include <err.h>
+
+struct context {
+   void *buf;
+
+   struct {
+      size_t offset, len;
+      bool with_offset_and_len;
+
+      struct {
+         FILE *src;
+      } write;
+
+      enum {
+         MODE_WRITE,
+         MODE_READ
+      } mode;
+   } op;
+
+   struct {
+      FILE *mem;
+      pid_t pid;
+   } proc;
+};
+
+static void
+usage(const char *argv0)
+{
+   fprintf(stderr, "usage: %s pid write file [offset len] < regions\n"
+                   "       %s pid read [offset len] < regions\n\n"
+                   "       regions must be in /proc/<pid>/maps format", argv0, argv0);
+   exit(EXIT_FAILURE);
+}
+
+static void
+context_init(struct context *ctx, int argc, const char *argv[])
+{
+   if (argc < 3)
+      usage(argv[0]);
+
+   *ctx = (struct context){0};
+   ctx->proc.pid = strtoull(argv[1], NULL, 10);
+
+   {
+      bool w, r;
+      const char *mode = argv[2];
+      if ((w = strcmp(mode, "write")) && (r = strcmp(mode, "read")))
+         err(EXIT_FAILURE, "mode must be write or read");
+
+      ctx->op.mode = (!w ? MODE_WRITE : MODE_READ);
+   }
+
+   if (ctx->op.mode == MODE_WRITE && argc < 4)
+      usage(argv[0]);
+
+   if (argc > (ctx->op.mode == MODE_WRITE) + 3) {
+      if (argc < (ctx->op.mode == MODE_WRITE) + 5)
+         usage(argv[0]);
+
+      ctx->op.offset = strtoull(argv[(ctx->op.mode == MODE_WRITE) + 3], NULL, 10);
+      ctx->op.len = strtoull(argv[(ctx->op.mode == MODE_WRITE) + 4], NULL, 10);
+      ctx->op.with_offset_and_len = true;
+   }
+
+   if (ctx->op.mode == MODE_WRITE && !(ctx->op.write.src = fopen(argv[3], "rb")))
+      err(EXIT_FAILURE, "fopen(%s)", argv[3]);
+
+   char path[128];
+   snprintf(path, sizeof(path), "/proc/%u/mem", ctx->proc.pid);
+   if (!(ctx->proc.mem = fopen(path, "w+b")))
+      err(EXIT_FAILURE, "fopen(%s)", path);
+}
+
+static void
+context_release(struct context *ctx)
+{
+   if (ctx->op.write.src)
+      fclose(ctx->op.write.src);
+
+   fclose(ctx->proc.mem);
+   free(ctx->buf);
+   *ctx = (struct context){0};
+}
+
+static void
+for_each_line_in_file(FILE *f, void (*cb)(const char *line, void *data), void *data)
+{
+   char *buffer = NULL;
+   size_t step = 1024, allocated = 0, written = 0, read = 0;
+   do {
+      if (written + read >= allocated && !(buffer = realloc(buffer, (allocated += step) + 1)))
+         err(EXIT_FAILURE, "realloc");
+      buffer[(written += read)] = 0;
+      size_t ate = 0;
+      for (char *line = buffer, *nl; (nl = strchr(line, '\n')); line = nl + 1) {
+         *nl = 0;
+         cb(line, data);
+         ate += nl + 1 - line;
+      }
+      memmove(buffer, buffer + ate, (written = written - ate));
+   } while ((read = fread(buffer + written, 1, allocated - written, f)));
+   free(buffer);
+}
+
+static void
+region_cb(const char *line, void *data)
+{
+   struct context *ctx = data;
+   unsigned long start, end, offset;
+   if (sscanf(line, "%lx-%lx %*s %lx", &start, &end, &offset) < 3) {
+      warnx("failed to parse mapping:\n%s", line);
+      return;
+   }
+
+   warnx("%s", line);
+
+   offset = (ctx->op.with_offset_and_len ? 0 : offset);
+   const size_t len = (ctx->op.with_offset_and_len ? ctx->op.len : end - start);
+   if (start + ctx->op.offset > end) {
+      warnx("write offset %lx is out of bounds", start + ctx->op.offset);
+      return;
+   }
+
+   if (len > (end - start - ctx->op.offset)) {
+      warnx("%zu bytes doesn't fit in the region", len);
+      return;
+   }
+
+   if (!len)
+      return;
+
+   if (!(ctx->buf = realloc(ctx->buf, len)))
+      err(EXIT_FAILURE, "realloc");
+
+   clearerr(ctx->proc.mem);
+   if (ctx->op.mode == MODE_WRITE) {
+      if (fseek(ctx->op.write.src, offset, SEEK_SET) != 0)
+         err(EXIT_FAILURE, "fseek");
+
+      const size_t rd = fread(ctx->buf, 1, len, ctx->op.write.src);
+
+      if (fseek(ctx->proc.mem, start + ctx->op.offset, SEEK_SET) != 0)
+         err(EXIT_FAILURE, "fseek");
+
+      const size_t wd = fwrite(ctx->buf, 1, rd, ctx->proc.mem);
+
+      if (ferror(ctx->proc.mem)) {
+         warn("fread(/proc/%u/mem)", ctx->proc.pid);
+      } else {
+         warnx("wrote %lu bytes from offset 0x%lx to offset 0x%lx", wd, offset, start + ctx->op.offset);
+      }
+   } else {
+      if (fseek(ctx->proc.mem, start + ctx->op.offset, SEEK_SET) != 0)
+         err(EXIT_FAILURE, "fseek");
+
+      const size_t rd = fread(ctx->buf, 1, len, ctx->proc.mem);
+
+      if (ferror(ctx->proc.mem))
+         warn("fread(/proc/%u/mem)", ctx->proc.pid);
+
+      if (fwrite(ctx->buf, 1, rd, stdout) != rd)
+         err(EXIT_FAILURE, "fwrite");
+   }
+}
+
+int
+main(int argc, const char *argv[])
+{
+   struct context ctx;
+   context_init(&ctx, argc, argv);
+
+   if (ptrace(PTRACE_ATTACH, ctx.proc.pid, NULL, NULL) == -1L)
+      err(EXIT_FAILURE, "ptrace(PTRACE_ATTACH, %u, NULL, NULL)", ctx.proc.pid);
+
+   {
+      int status;
+      if (waitpid(ctx.proc.pid, &status, 0) == -1 || !WIFSTOPPED(status))
+         err(EXIT_FAILURE, "waitpid");
+   }
+
+   for_each_line_in_file(stdin, region_cb, &ctx);
+   ptrace(PTRACE_DETACH, ctx.proc.pid, 1, 0);
+   context_release(&ctx);
+   return EXIT_SUCCESS;
+}
-- 
cgit v1.2.3-70-g09d2