[4282] in BarnOwl Developers

home help back first fref pref prev next nref lref last post

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

home help back first fref pref prev next nref lref last post