[PATCH] OpenBSD: Hoist loading save file to drop filesystem read-access

2 replies

[PATCH] OpenBSD: Hoist loading save file to drop filesystem read-access

From: Klemens Nanni
After TLS cert/key files, the save file is the only file being read from;
do so before pleding and drop the "rpath" promise all together:  log files
will only be created and written to.
---
 chat.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/chat.c b/chat.c
index 4f3c233..e01b511 100644
--- a/chat.c
+++ b/chat.c
@@ -276,6 +276,10 @@ int main(int argc, char *argv[]) {
 	ircConfig(insecure, trust, cert, priv);
 
 	uiInitEarly();
+	if (save) {
+		uiLoad(save);
+		atexit(exitSave);
+	}
 
 #ifdef __OpenBSD__
 	if (self.restricted) {
@@ -288,7 +292,7 @@ int main(int argc, char *argv[]) {
 
 	char promises[64] = "stdio tty";
 	char *ptr = &promises[strlen(promises)], *end = &promises[sizeof(promises)];
-	if (save || logEnable) ptr = seprintf(ptr, end, " rpath wpath cpath");
+	if (save || logEnable) ptr = seprintf(ptr, end, " wpath cpath");
 	if (!self.restricted) ptr = seprintf(ptr, end, " proc exec");
 
 	char *promisesFinal = strdup(promises);
@@ -299,10 +303,6 @@ int main(int argc, char *argv[]) {
 	if (error) err(EX_OSERR, "pledge");
 #endif
 
-	if (save) {
-		uiLoad(save);
-		atexit(exitSave);
-	}
 	uiShowID(Network);
 	uiFormat(
 		Network, Cold, NULL,
-- 
2.32.0

4 replies

[PATCH 1/4] OpenBSD: Hoist loading save file to drop filesystem read-access

From: Klemens Nanni
After TLS cert/key files, the save file is the only file being read from;
do so before pleding and drop the "rpath" promise all together:  log files
will only be created and written to.
---
 chat.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/chat.c b/chat.c
index 4f3c233..e01b511 100644
--- a/chat.c
+++ b/chat.c
@@ -276,6 +276,10 @@ int main(int argc, char *argv[]) {
 	ircConfig(insecure, trust, cert, priv);
 
 	uiInitEarly();
+	if (save) {
+		uiLoad(save);
+		atexit(exitSave);
+	}
 
 #ifdef __OpenBSD__
 	if (self.restricted) {
@@ -288,7 +292,7 @@ int main(int argc, char *argv[]) {
 
 	char promises[64] = "stdio tty";
 	char *ptr = &promises[strlen(promises)], *end = &promises[sizeof(promises)];
-	if (save || logEnable) ptr = seprintf(ptr, end, " rpath wpath cpath");
+	if (save || logEnable) ptr = seprintf(ptr, end, " wpath cpath");
 	if (!self.restricted) ptr = seprintf(ptr, end, " proc exec");
 
 	char *promisesFinal = strdup(promises);
@@ -299,10 +303,6 @@ int main(int argc, char *argv[]) {
 	if (error) err(EX_OSERR, "pledge");
 #endif
 
-	if (save) {
-		uiLoad(save);
-		atexit(exitSave);
-	}
 	uiShowID(Network);
 	uiFormat(
 		Network, Cold, NULL,
-- 
2.32.0

1 reply

[PATCH 2/4] Rename file to saveFile

From: Klemens Nanni
Separate churn from actual change in upcoming diff,
no functional change.
---
 ui.c | 80 ++++++++++++++++++++++++++++++------------------------------
 1 file changed, 40 insertions(+), 40 deletions(-)

diff --git a/ui.c b/ui.c
index 796fd6b..d8cb3e5 100644
--- a/ui.c
+++ b/ui.c
@@ -1129,40 +1129,40 @@ static int writeString(FILE *file, const char *str) {
 }
 
 int uiSave(const char *name) {
-	FILE *file = dataOpen(name, "w");
-	if (!file) return -1;
+	FILE *saveFile = dataOpen(name, "w");
+	if (!saveFile) return -1;
 
 	int error = 0
-		|| writeTime(file, Signatures[7])
-		|| writeTime(file, self.pos);
+		|| writeTime(saveFile, Signatures[7])
+		|| writeTime(saveFile, self.pos);
 	if (error) return error;
 	for (uint num = 0; num < windows.len; ++num) {
 		const struct Window *window = windows.ptrs[num];
 		error = 0
-			|| writeString(file, idNames[window->id])
-			|| writeTime(file, window->mute)
-			|| writeTime(file, window->time)
-			|| writeTime(file, window->thresh)
-			|| writeTime(file, window->heat)
-			|| writeTime(file, window->unreadSoft)
-			|| writeTime(file, window->unreadWarm);
+			|| writeString(saveFile, idNames[window->id])
+			|| writeTime(saveFile, window->mute)
+			|| writeTime(saveFile, window->time)
+			|| writeTime(saveFile, window->thresh)
+			|| writeTime(saveFile, window->heat)
+			|| writeTime(saveFile, window->unreadSoft)
+			|| writeTime(saveFile, window->unreadWarm);
 		if (error) return error;
 		for (size_t i = 0; i < BufferCap; ++i) {
 			const struct Line *line = bufferSoft(window->buffer, i);
 			if (!line) continue;
 			error = 0
-				|| writeTime(file, line->time)
-				|| writeTime(file, line->heat)
-				|| writeString(file, line->str);
+				|| writeTime(saveFile, line->time)
+				|| writeTime(saveFile, line->heat)
+				|| writeString(saveFile, line->str);
 			if (error) return error;
 		}
-		error = writeTime(file, 0);
+		error = writeTime(saveFile, 0);
 		if (error) return error;
 	}
 	return 0
-		|| writeString(file, "")
-		|| urlSave(file)
-		|| fclose(file);
+		|| writeString(saveFile, "")
+		|| urlSave(saveFile)
+		|| fclose(saveFile);
 }
 
 static time_t readTime(FILE *file) {
@@ -1179,51 +1179,51 @@ static ssize_t readString(FILE *file, char **buf, size_t *cap) {
 }
 
 void uiLoad(const char *name) {
-	FILE *file = dataOpen(name, "r");
-	if (!file) {
+	FILE *saveFile = dataOpen(name, "r");
+	if (!saveFile) {
 		if (errno != ENOENT) exit(EX_NOINPUT);
-		file = dataOpen(name, "w");
-		if (!file) exit(EX_CANTCREAT);
-		fclose(file);
+		saveFile = dataOpen(name, "w");
+		if (!saveFile) exit(EX_CANTCREAT);
+		fclose(saveFile);
 		return;
 	}
 
 	time_t signature;
-	fread(&signature, sizeof(signature), 1, file);
-	if (ferror(file)) err(EX_IOERR, "fread");
-	if (feof(file)) {
-		fclose(file);
+	fread(&signature, sizeof(signature), 1, saveFile);
+	if (ferror(saveFile)) err(EX_IOERR, "fread");
+	if (feof(saveFile)) {
+		fclose(saveFile);
 		return;
 	}
 	size_t version = signatureVersion(signature);
 
 	if (version > 1) {
-		self.pos = readTime(file);
+		self.pos = readTime(saveFile);
 	}
 
 	char *buf = NULL;
 	size_t cap = 0;
-	while (0 < readString(file, &buf, &cap) && buf[0]) {
+	while (0 < readString(saveFile, &buf, &cap) && buf[0]) {
 		struct Window *window = windows.ptrs[windowFor(idFor(buf))];
-		if (version > 3) window->mute = readTime(file);
-		if (version > 6) window->time = readTime(file);
-		if (version > 5) window->thresh = readTime(file);
+		if (version > 3) window->mute = readTime(saveFile);
+		if (version > 6) window->time = readTime(saveFile);
+		if (version > 5) window->thresh = readTime(saveFile);
 		if (version > 0) {
-			window->heat = readTime(file);
-			window->unreadSoft = readTime(file);
-			window->unreadWarm = readTime(file);
+			window->heat = readTime(saveFile);
+			window->unreadSoft = readTime(saveFile);
+			window->unreadWarm = readTime(saveFile);
 		}
 		for (;;) {
-			time_t time = readTime(file);
+			time_t time = readTime(saveFile);
 			if (!time) break;
-			enum Heat heat = (version > 2 ? readTime(file) : Cold);
-			readString(file, &buf, &cap);
+			enum Heat heat = (version > 2 ? readTime(saveFile) : Cold);
+			readString(saveFile, &buf, &cap);
 			bufferPush(window->buffer, COLS, window->thresh, heat, time, buf);
 		}
 		windowReflow(window);
 	}
-	urlLoad(file, version);
+	urlLoad(saveFile, version);
 
 	free(buf);
-	fclose(file);
+	fclose(saveFile);
 }
-- 
2.32.0

1 reply

[PATCH 3/4] Open save file once in uiLoad() and keep it open until uiSave()

From: Klemens Nanni
Opening the same file *path* twice is a TOCTOU, although not a critical
one: worst case we load from one file and save to another - the impact
depends on how and when catgirl is started the next anyway.

More importantly, keeping the file handle open at runtime allows us to
drop all filesystem related promises for `-s/save' on OpenBSD.

uiLoad() now opens "r+", meaning "Open for reading and writing." up
front so uiSave() can write to it.  In the case of a nonexistent save
file, it now opens with "a" meaning "Open for writing.  The file is
created if it does not exist.", i.e. the same write/create semantics as
"w" except uiLoad() no longer truncates. existing files.

uiSave() now truncates the save file to avoid appending in general;
Changing uiLoad()'s second case from "w" to "a" simply avoids duplicate
truncation.
---
 chat.c |  2 +-
 chat.h |  2 +-
 ui.c   | 11 ++++-------
 3 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/chat.c b/chat.c
index e01b511..e6cd270 100644
--- a/chat.c
+++ b/chat.c
@@ -83,7 +83,7 @@ struct Self self = { .color = Default };
 
 static const char *save;
 static void exitSave(void) {
-	int error = uiSave(save);
+	int error = uiSave();
 	if (error) {
 		warn("%s", save);
 		_exit(EX_IOERR);
diff --git a/chat.h b/chat.h
index 327262b..8c99cf5 100644
--- a/chat.h
+++ b/chat.h
@@ -312,7 +312,7 @@ void uiFormat(
 	uint id, enum Heat heat, const time_t *time, const char *format, ...
 ) __attribute__((format(printf, 4, 5)));
 void uiLoad(const char *name);
-int uiSave(const char *name);
+int uiSave(void);
 
 enum { BufferCap = 1024 };
 struct Buffer;
diff --git a/ui.c b/ui.c
index d8cb3e5..68b1fe9 100644
--- a/ui.c
+++ b/ui.c
@@ -1128,10 +1128,10 @@ static int writeString(FILE *file, const char *str) {
 	return (fwrite(str, strlen(str) + 1, 1, file) ? 0 : -1);
 }
 
-int uiSave(const char *name) {
-	FILE *saveFile = dataOpen(name, "w");
-	if (!saveFile) return -1;
+static FILE *saveFile;
 
+int uiSave(void) {
+	if (ftruncate(fileno(saveFile), 0) == -1) err(EX_IOERR, "ftruncate");
 	int error = 0
 		|| writeTime(saveFile, Signatures[7])
 		|| writeTime(saveFile, self.pos);
@@ -1179,12 +1179,11 @@ static ssize_t readString(FILE *file, char **buf, size_t *cap) {
 }
 
 void uiLoad(const char *name) {
-	FILE *saveFile = dataOpen(name, "r");
+	saveFile = dataOpen(name, "r+");
 	if (!saveFile) {
 		if (errno != ENOENT) exit(EX_NOINPUT);
 		saveFile = dataOpen(name, "w");
 		if (!saveFile) exit(EX_CANTCREAT);
-		fclose(saveFile);
 		return;
 	}
 
@@ -1192,7 +1191,6 @@ void uiLoad(const char *name) {
 	fread(&signature, sizeof(signature), 1, saveFile);
 	if (ferror(saveFile)) err(EX_IOERR, "fread");
 	if (feof(saveFile)) {
-		fclose(saveFile);
 		return;
 	}
 	size_t version = signatureVersion(signature);
@@ -1225,5 +1223,4 @@ void uiLoad(const char *name) {
 	urlLoad(saveFile, version);
 
 	free(buf);
-	fclose(saveFile);
 }
-- 
2.32.0

1 reply

[PATCH 4/4] OpenBSD: Drop now unneeded file system access for save file

From: Klemens Nanni
All opening happens before unveil/pledge and the file handle is kept
open read/write so it can be used without any pledge.

Simpler/less code and less chances to write other files (accidentially).
---
 chat.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/chat.c b/chat.c
index e6cd270..d92240d 100644
--- a/chat.c
+++ b/chat.c
@@ -283,16 +283,15 @@ int main(int argc, char *argv[]) {
 
 #ifdef __OpenBSD__
 	if (self.restricted) {
-		if (save || logEnable) {
+		if (logEnable) {
 			dataMkdir("");
 			unveilData("");
 		}
-		if (save) unveilData(save);
 	}
 
 	char promises[64] = "stdio tty";
 	char *ptr = &promises[strlen(promises)], *end = &promises[sizeof(promises)];
-	if (save || logEnable) ptr = seprintf(ptr, end, " wpath cpath");
+	if (logEnable) ptr = seprintf(ptr, end, " wpath cpath");
 	if (!self.restricted) ptr = seprintf(ptr, end, " proc exec");
 
 	char *promisesFinal = strdup(promises);
-- 
2.32.0