Compare commits

..

10 Commits

Author SHA1 Message Date
6faf91a806
add an ignore filter based on substring of the file 2023-11-09 12:26:33 -08:00
Oswald Buddenhagen
bb5e98e9ec bump version 2021-12-03 11:56:16 +01:00
Oswald Buddenhagen
f2b1e80033 modernize configure.ac 2021-12-03 11:56:16 +01:00
Oswald Buddenhagen
e686f88318 don't complain about concurrent flagging as deleted
the result of propagating a deletion is flagging as deleted, so shut up
if the only remote change is exactly that.
2021-12-03 11:56:16 +01:00
Oswald Buddenhagen
51673214ab fix read beyond end of input in copy_msg_convert()
the input isn't necessarily null-terminated (it currently is for imap,
but not for maildir), so if the message ended somewhere within the
header field name, we'd read beyond its end, which theoretically could
cause a crash. no other adverse effects could result, as we'd stop
processing such a broken message right afterwards.

amends 70bad661.
2021-12-03 11:46:33 +01:00
Oswald Buddenhagen
127003ee37 reject unreasonably long mailbox names from IMAP LIST
this wasn't really a security problem, as the name mapping we actually
do does not change the string length, and the iteration was already
safe after the literal length fix, but it's still better to catch weird
input.
2021-12-01 10:07:40 +01:00
Oswald Buddenhagen
92921b1d3b reject messages that grow too large due to conversion
that shouldn't really be a problem, as we have 2GB of headroom, and most
growth would happen when sending an all-newlines message from maildir to
imap (due to CR additions), which is mostly non-critical. but better
safe than sorry.
2021-12-01 10:07:40 +01:00
Oswald Buddenhagen
bc15e571b6 report conversion errors directly in copy_msg_convert()
that makes it easier to report various conditions without introducing
separate error codes.
2021-12-01 10:07:40 +01:00
Oswald Buddenhagen
ba13362a52 deal with oversized messages in maildirs
don't try to read messages > 2G, as that will only lead to trouble down
the line.

this wouldn't have worked on linux anyway (we read in one chunk, and
that is limited to (2^31 - 2^12) on all architectures), but on
platforms were big reads work, this was a security problem if one
synchronized other users' maildirs.

as a minor fix on the side, we now also clip the reported message size,
so MaxSize works for excessively big messages.
2021-12-01 10:07:40 +01:00
Oswald Buddenhagen
463272eab8 CVE-2021-3657: reject excessively large IMAP literals
we didn't limit the 32-bit size of literals so far, which, given that we
use int-sized lengths & offsets, permitted all kinds of buffer
overflows. malicious/compromised servers may have been able to exploit
this. actual email senders would be constrained by size limits for
delivered mails, and to cause more than a crash they'd have to predict
the exact size of the final message.

we now limit to 2GB, which, given that we use unsigned ints since
e2d3b4d55 (v1.4.0), gives the handlers downstream plenty of headroom.

an alternative would have been using 64-bit offsets, but this seems like
major overkill, even if IMAP4rev2 recently mandated it (we talk only
IMAP4rev1, so we can ignore it).
2021-12-01 10:07:24 +01:00
4 changed files with 49 additions and 20 deletions

View File

@ -1,4 +1,4 @@
AC_INIT([isync], [1.4.3]) AC_INIT([isync], [1.4.4])
AC_CONFIG_HEADERS([autodefs.h]) AC_CONFIG_HEADERS([autodefs.h])
AC_CANONICAL_TARGET AC_CANONICAL_TARGET
@ -62,8 +62,8 @@ if test "x$ob_cv_perl_ver" = "xno"; then
fi fi
AC_CACHE_CHECK([whether strftime supports %z], ob_cv_strftime_z, AC_CACHE_CHECK([whether strftime supports %z], ob_cv_strftime_z,
[AC_TRY_RUN( [AC_RUN_IFELSE([AC_LANG_SOURCE([[
[#include <time.h> #include <time.h>
#include <string.h> #include <string.h>
int main(void) int main(void)
@ -73,7 +73,7 @@ int main(void)
strftime(buf, sizeof(buf), "%z", localtime(&t)); strftime(buf, sizeof(buf), "%z", localtime(&t));
return !(buf[0] == '+' || buf[0] == '-'); return !(buf[0] == '+' || buf[0] == '-');
} }
], [ob_cv_strftime_z=yes], [ob_cv_strftime_z=no], [ob_cv_strftime_z="yes (assumed)"])]) ]])], [ob_cv_strftime_z=yes], [ob_cv_strftime_z=no], [ob_cv_strftime_z="yes (assumed)"])])
if test "x$ob_cv_strftime_z" = x"no"; then if test "x$ob_cv_strftime_z" = x"no"; then
AC_MSG_ERROR([libc lacks necessary feature]) AC_MSG_ERROR([libc lacks necessary feature])
fi fi
@ -96,7 +96,7 @@ fi
have_ssl_paths= have_ssl_paths=
AC_ARG_WITH(ssl, AC_ARG_WITH(ssl,
AC_HELP_STRING([--with-ssl[=PATH]], [where to look for SSL [detect]]), AS_HELP_STRING([--with-ssl[=PATH]], [where to look for SSL [detect]]),
[ob_cv_with_ssl=$withval]) [ob_cv_with_ssl=$withval])
if test "x$ob_cv_with_ssl" != xno; then if test "x$ob_cv_with_ssl" != xno; then
case $ob_cv_with_ssl in case $ob_cv_with_ssl in
@ -193,12 +193,13 @@ AC_CACHE_CHECK([for Berkeley DB >= 4.1], ac_cv_berkdb4,
[ac_cv_berkdb4=no [ac_cv_berkdb4=no
sav_LIBS=$LIBS sav_LIBS=$LIBS
LIBS="$LIBS -ldb" LIBS="$LIBS -ldb"
AC_TRY_LINK([#include <db.h>], AC_LINK_IFELSE([AC_LANG_PROGRAM(
[#include <db.h>],
[DB *db; [DB *db;
db_create(&db, 0, 0); db_create(&db, 0, 0);
db->truncate(db, 0, 0, 0); db->truncate(db, 0, 0, 0);
db->open(db, 0, "foo", "foo", DB_HASH, DB_CREATE, 0)], db->open(db, 0, "foo", "foo", DB_HASH, DB_CREATE, 0);
[ac_cv_berkdb4=yes]) ])], [ac_cv_berkdb4=yes], [])
LIBS=$sav_LIBS LIBS=$sav_LIBS
]) ])
if test "x$ac_cv_berkdb4" = xyes; then if test "x$ac_cv_berkdb4" = xyes; then

View File

@ -877,6 +877,11 @@ parse_imap_list( imap_store_t *ctx, char **sp, parse_list_state_t *sts )
bytes = (int)(cur->len = strtoul( s + 1, &s, 10 )); bytes = (int)(cur->len = strtoul( s + 1, &s, 10 ));
if (*s != '}' || *++s) if (*s != '}' || *++s)
goto bail; goto bail;
if ((uint)bytes >= INT_MAX) {
error( "IMAP error: excessively large literal from %s "
"- THIS MIGHT BE AN ATTEMPT TO HACK YOU!\n", ctx->conn.name );
goto bail;
}
s = cur->val = nfmalloc( cur->len + 1 ); s = cur->val = nfmalloc( cur->len + 1 );
s[cur->len] = 0; s[cur->len] = 0;
@ -1434,6 +1439,10 @@ parse_list_rsp_p2( imap_store_t *ctx, list_t *list, char *cmd ATTR_UNUSED )
} }
arg = list->val; arg = list->val;
argl = (int)list->len; argl = (int)list->len;
if (argl > 1000) {
warn( "IMAP warning: ignoring unreasonably long mailbox name '%.100s[...]'\n", arg );
return LIST_OK;
}
// The server might be weird and have a non-uppercase INBOX. It // The server might be weird and have a non-uppercase INBOX. It
// may legitimately do so, but we need the canonical spelling. // may legitimately do so, but we need the canonical spelling.
normalize_INBOX( ctx, arg, argl ); normalize_INBOX( ctx, arg, argl );

View File

@ -991,9 +991,12 @@ maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
#endif /* USE_DB */ #endif /* USE_DB */
return DRV_BOX_BAD; return DRV_BOX_BAD;
} }
const char *filter = getenv("MBSYNC_MAILDIR_IGNORE");
while ((e = readdir( d ))) { while ((e = readdir( d ))) {
if (*e->d_name == '.') if (*e->d_name == '.')
continue; continue;
if (filter && strstr(e->d_name, filter))
continue;
ctx->total_msgs++; ctx->total_msgs++;
ctx->recent_msgs += i; ctx->recent_msgs += i;
#ifdef USE_DB #ifdef USE_DB
@ -1168,7 +1171,8 @@ maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
} }
goto retry; goto retry;
} }
entry->size = (uint)st.st_size; // The clipped value is good enough for MaxSize comparisons.
entry->size = st.st_size > UINT_MAX ? UINT_MAX : (uint)st.st_size;
} }
if (want_tuid || want_msgid) { if (want_tuid || want_msgid) {
if (!(f = fopen( buf, "r" ))) { if (!(f = fopen( buf, "r" ))) {
@ -1563,12 +1567,17 @@ maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data, int minimal
} }
} }
fstat( fd, &st ); fstat( fd, &st );
if (st.st_size > INT_MAX) {
error( "Maildir error: %s is too big", buf );
goto mbad;
}
data->len = st.st_size; data->len = st.st_size;
if (data->date == -1) if (data->date == -1)
data->date = st.st_mtime; data->date = st.st_mtime;
data->data = nfmalloc( data->len ); data->data = nfmalloc( data->len );
if (read( fd, data->data, data->len ) != data->len) { if (read( fd, data->data, data->len ) != data->len) {
sys_error( "Maildir error: cannot read %s", buf ); sys_error( "Maildir error: cannot read %s", buf );
mbad:
close( fd ); close( fd );
cb( DRV_MSG_BAD, aux ); cb( DRV_MSG_BAD, aux );
return; return;

View File

@ -406,7 +406,7 @@ copy_msg_bytes( char **out_ptr, const char *in_buf, uint *in_idx, uint in_len, i
} }
static int static int
copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars ) copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars, int t )
{ {
char *in_buf = vars->data.data; char *in_buf = vars->data.data;
uint in_len = vars->data.len; uint in_len = vars->data.len;
@ -428,9 +428,10 @@ copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars )
if (!vars->minimal) if (!vars->minimal)
goto oke; goto oke;
} else { } else {
if (break2 == UINT_MAX && vars->minimal && !strncasecmp( in_buf + start, "Subject:", 8 )) { if (break2 == UINT_MAX && vars->minimal &&
starts_with_upper( in_buf + start, (int)(in_len - start), "SUBJECT:", 8 )) {
break2 = start + 8; break2 = start + 8;
if (in_buf[break2] == ' ') if (break2 < in_len && in_buf[break2] == ' ')
break2++; break2++;
} }
lines++; lines++;
@ -451,7 +452,8 @@ copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars )
goto nloop; goto nloop;
} }
} }
/* invalid message */ warn( "Warning: message %u from %s has incomplete header; skipping.\n",
vars->msg->uid, str_fn[1-t] );
free( in_buf ); free( in_buf );
return 0; return 0;
oke: oke:
@ -493,6 +495,12 @@ copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars )
} }
vars->data.len = in_len + extra; vars->data.len = in_len + extra;
if (vars->data.len > INT_MAX) {
warn( "Warning: message %u from %s is too big after conversion; skipping.\n",
vars->msg->uid, str_fn[1-t] );
free( in_buf );
return 0;
}
char *out_buf = vars->data.data = nfmalloc( vars->data.len ); char *out_buf = vars->data.data = nfmalloc( vars->data.len );
idx = 0; idx = 0;
if (vars->srec) { if (vars->srec) {
@ -556,9 +564,7 @@ msg_fetched( int sts, void *aux )
scr = (svars->drv[1-t]->get_caps( svars->ctx[1-t] ) / DRV_CRLF) & 1; scr = (svars->drv[1-t]->get_caps( svars->ctx[1-t] ) / DRV_CRLF) & 1;
tcr = (svars->drv[t]->get_caps( svars->ctx[t] ) / DRV_CRLF) & 1; tcr = (svars->drv[t]->get_caps( svars->ctx[t] ) / DRV_CRLF) & 1;
if (vars->srec || scr != tcr) { if (vars->srec || scr != tcr) {
if (!copy_msg_convert( scr, tcr, vars )) { if (!copy_msg_convert( scr, tcr, vars, t )) {
warn( "Warning: message %u from %s has incomplete header.\n",
vars->msg->uid, str_fn[1-t] );
vars->cb( SYNC_NOGOOD, 0, vars ); vars->cb( SYNC_NOGOOD, 0, vars );
return; return;
} }
@ -1690,7 +1696,11 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux
JLOG( "> %u %u 0", (srec->uid[F], srec->uid[N]), "near side expired, orphaning far side" ); JLOG( "> %u %u 0", (srec->uid[F], srec->uid[N]), "near side expired, orphaning far side" );
srec->uid[N] = 0; srec->uid[N] = 0;
} else { } else {
if (srec->msg[t] && (srec->msg[t]->status & M_FLAGS) && srec->msg[t]->flags != srec->flags) if (srec->msg[t] && (srec->msg[t]->status & M_FLAGS) &&
// Ignore deleted flag, as that's what we'll change ourselves ...
(((srec->msg[t]->flags & ~F_DELETED) != (srec->flags & ~F_DELETED)) ||
// ... except for undeletion, as that's the opposite.
(!(srec->msg[t]->flags & F_DELETED) && (srec->flags & F_DELETED))))
notice( "Notice: conflicting changes in (%u,%u)\n", srec->uid[F], srec->uid[N] ); notice( "Notice: conflicting changes in (%u,%u)\n", srec->uid[F], srec->uid[N] );
if (svars->chan->ops[t] & OP_DELETE) { if (svars->chan->ops[t] & OP_DELETE) {
debug( " %sing delete\n", str_hl[t] ); debug( " %sing delete\n", str_hl[t] );