[4282] in BarnOwl Developers
BarnOwl 1.10: a zephyr's CC: line is used as a log file name
daemon@ATHENA.MIT.EDU (Daniel Clark)
Sun Nov 1 00:50:55 2026
From: Daniel Clark <djbclark@gmail.com>
Date: Sun, 1 Nov 2026 00:50:22 -0400
To: barnowl-dev@mit.edu
Hello BarnOwl developers,
I found a security bug in BarnOwl 1.10 logging: a zephyr's CC: line is
used as a log file name without the checks that the sender's own name
gets. Any zephyr sender can make the receiving client append a message
to any file the user can write, or create it. I have also reported it
to security@debian.org (2026-11-01). I am sending it to you so that it
is fixed upstream and so that you can decide how to tell BarnOwl users.
WHAT HAPPENS
owl_log_incoming (logging.c) checks the sender's name before using it as
a file name under logpath: 1 to 35 printable ASCII characters, first
character alphanumeric, no '/', not "." or "..". For a personal zephyr
it then also logs the message to every name on its CC list
(owl_message_get_cc_without_recipient: the first body line, if it starts
with "CC:"). Those names come from the sender and get none of those
checks: g_build_filename(logpath, short_zuser(name), NULL), then
fopen(..., "a") on the logger thread. owl_log_outgoing has the same
loop, for the user's own CC lines.
IMPACT
A personal zephyr whose body starts with the line "CC:
../../.ssh/authorized_keys", followed by a line holding an SSH public
key, makes the receiving client append the whole logged entry (header
lines, then the body) to ~/.ssh/authorized_keys. sshd ignores the header
lines and accepts the key line. Appending to ~/.bashrc or creating
~/.ssh/rc gives code execution the same way. It needs personal logging
on ("set logging on", loggingdirection "in" or "both", the documented
setup), no authentication and no action by the user.
AFFECTED
BarnOwl 1.10 (tag barnowl-1.10), tested as Debian barnowl 1.10-2+b9 on
Debian 13. I did not check other versions; the code looks old, so
earlier ones are probably affected too.
REPRODUCTION (scratch directory; nothing outside it is touched)
Use a scratch HOME with the config dir /tmp/T/home/.owl and logpath
/tmp/T/zlog/people. Put this in /tmp/T/home/.owl/startup:
set logging on
set loggingdirection both
set logpath /tmp/T/zlog/people
perl BarnOwl::queue_message(BarnOwl::Message->new(
type=>'zephyr', direction=>'in', class=>'message',
instance=>'personal', sender=>'eve@ATHENA.MIT.EDU',
recipient=>'user@ATHENA.MIT.EDU', private=>1,
isprivate=>'true', realm=>'ATHENA.MIT.EDU', opcode=>'',
auth=>'NO', host=>'h', hostname=>'h', zsig=>'z',
body=>"CC: ../../victim\nssh-ed25519 AAAAFAKEKEY attacker\n",
time=>scalar(localtime), unix_time=>time()))
perl our $t = BarnOwl::Timer->new({after=>4,
cb=>sub{BarnOwl::command("quit")}})
Then run: HOME=/tmp/T/home script -qfc 'barnowl -n -s /tmp/T/home/.owl'
/dev/null. With 1.10-2+b9, /tmp/T/victim exists (outside logpath) and
holds the logged entry. With the patch below it is not created, and a CC
to an ordinary user name is still logged. A real zephyr goes through the
same function.
TEMPORARY WORKAROUND FOR USERS
Until a fixed BarnOwl is installed, users can stop the bug by turning
personal-zephyr logging off:
- Put "set logging off" in ~/.owl/startup (replacing "set logging on")
and run ":set logging off" in a running client. Personal zephyrs are
then not logged, in either direction. Class logging ("set
classlogging on") is unaffected, because class names get the sender-
name checks and have no CC handling.
- "set loggingdirection out" should also stop incoming personals from
being logged while keeping a log of what you send. I have not tested
that variant; the "logging off" one is based on reading the code and
on how the logging variables gate the logger, and I did not run it
against the exploit.
- Weaker, partial measures: make ~/.ssh/authorized_keys, ~/.bashrc and
~/.profile read-only (chmod 400, or chattr +i as root). This does
not help against files that do not exist yet, such as ~/.ssh/rc or
~/.bash_login, and any file the user can write stays reachable.
- Run the client in a sandbox (bwrap, firejail, a separate
unprivileged account) that can write only ~/.owl and the log
directory. I have not tried this.
FIX
Apply the sender-name rules to every CC name, in both loops. This patch
against 1.10 does that (it also works as a Debian quilt patch,
debian/patches/0001-log-validate-cc-names.patch). I tested the name
function on its own, and the behaviour above with a Debian 1.10-2 build
plus this patch. I did not review the jabber and aim branches of the
logger, which also build file names from message data.
--- a/logging.c
+++ b/logging.c
@@ -205,6 +205,26 @@
g_free(buffer);
}
+/* A name taken from a message is used as a file name under the log
directory only if
+ * it could be a user name: 1-35 printable ASCII characters, the
first alphanumeric,
+ * no '/', and not "." or "..". The CC: line of a zephyr is written
by its sender, so
+ * without this a CC of "../../.ssh/authorized_keys" makes us append
the message to
+ * that file (CVE pending). */
+static int owl_log_name_is_safe(const char *name)
+{
+ size_t i, len;
+
+ if (!name) return 0;
+ len = strlen(name);
+ if (len < 1 || len > 35) return 0;
+ if (!g_ascii_isalnum(name[0])) return 0;
+ for (i = 0; i < len; i++) {
+ if (name[i] < '!' || name[i] >= '~' || name[i] == '/') return 0;
+ }
+ if (!strcmp(name, ".") || !strcmp(name, "..")) return 0;
+ return 1;
+}
+
void owl_log_outgoing(const owl_message *m)
{
char *filename, *logpath;
@@ -220,10 +240,12 @@
cc = owl_message_get_cc_without_recipient(m);
while (cc != NULL) {
temp = short_zuser(cc->data);
- filename = g_build_filename(logpath, temp, NULL);
- owl_log_append(m, filename);
+ if (owl_log_name_is_safe(temp)) {
+ filename = g_build_filename(logpath, temp, NULL);
+ owl_log_append(m, filename);
+ g_free(filename);
+ }
- g_free(filename);
g_free(temp);
g_free(cc->data);
cc = g_list_delete_link(cc, cc);
@@ -410,7 +432,7 @@
cc = owl_message_get_cc_without_recipient(m);
while (cc != NULL) {
temp = short_zuser(cc->data);
- if (strcasecmp(temp, frombuff) != 0) {
+ if (strcasecmp(temp, frombuff) != 0 && owl_log_name_is_safe(temp)) {
filename = g_build_filename(logpath, temp, NULL);
owl_log_append(m, filename);
g_free(filename);
I can send a pull request if you prefer that, or answer questions. A CVE
has not been requested; Debian security may assign one, and you may want
to coordinate with them on the timing of any announcement. If you need
more detail, reply to me directly.
Daniel J. B. Clark
djbclark@gmail.com
djbclark@mit.edu