cyrusimap / cyrus-imapd

Cyrus IMAP is an email, contacts and calendar server
http://cyrusimap.org
Other
549 stars 150 forks source link

CalDav POST collection-url -> http-3.0 crash #2346

Closed dilyanpalauzov closed 6 years ago

dilyanpalauzov commented 6 years ago

Sending

curl -u X:Y -XPOST -H"Content-Type: text/calendar" --data "
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Ximian//NONSGML Test Call//EN
VERSION:2.0
BEGIN:VEVENT
DTSTAMP:20170528T094321Z
DTSTART;VALUE=DATE:20180528
SUMMARY:A BC
STATUS:CONFIRMED
TRANSP:TRANSPARENT
RECURRENCE-ID;VALUE=DATE:20180528
DTEND;VALUE=DATE:20180529
RRULE:FREQ=YEARLY
CLASS:PUBLIC
SEQUENCE:1
CREATED:20180506T183417Z
LAST-MODIFIED:20180506T183417Z
END:VEVENT
END:VCALENDAR" http://server/dav/calendars/user/X/Default/

causes httpd-3.0 to crash with this backtrace:

#0  strhash (string=string@entry=0x0) at lib/strhash.c:50
        ret_val = 0
        i = <optimized out>

#1  0x0000000000426685 in caldav_import (txn=txn@entry=0x7fffab891630, obj=obj@entry=0x1f3bc70, mailbox=0x1f3c688, 
    destdb=destdb@entry=0x1f3bd30, root=root@entry=0x1f3b650, ns=ns@entry=0x7fffab891570, flags=0)
    at imap/http_caldav.c:2923
        newical = 0x1f373e0
        tzrock = {
          old = 0x1f3bc70, 
          new = 0x1f373e0
        }
        ret = <optimized out>
        ical = 0x1f3bc70
        comp = <optimized out>
        next = 0x0
        kind = <optimized out>
        prodid = 0x1f3aa50
        version = 0x1f3a9c0
        calscale = 0x1f3ad60
        uid = 0x0
        caldavdb = <optimized out>
        resp = <optimized out>
        propstat = <optimized out>
        prop = <optimized out>
        error = <optimized out>
        post_count = 1
        len = <optimized out>

#2  0x00000000004454a7 in dav_post_import (pparams=0x48b2a0 <caldav_params>, txn=0x7fffab891630)
    at imap/http_dav.c:6494
        rights = <optimized out>
        mime = <optimized out>
        davdb = 0x1f3bd30
        root = 0x1f3b650
        ns = {0x1f379b0, 0x1f3a970, 0x0, 0x0, 0x1f3adf0, 0x0, 0x0, 0x0}
        ret = <optimized out>
        r = <optimized out>
        precond = <optimized out>
        hdr = <optimized out>
        obj = 0x1f3bc70
        outdoc = 0x1f3bbb0
        mailbox = 0x1f3c688
        qdiffs = {413, 0, 0, 0}
        ret = <optimized out>
        r = <optimized out>
        precond = <optimized out>
        rights = <optimized out>
        hdr = <optimized out>
        mime = <optimized out>
        mailbox = <optimized out>
        qdiffs = <optimized out>
        davdb = <optimized out>
        obj = <optimized out>
        outdoc = <optimized out>
        root = <optimized out>
        ns = <optimized out>
        be = <optimized out>

#3  meth_post (txn=0x7fffab891630, params=0x48b2a0 <caldav_params>) at imap/http_dav.c:6563
        hdr = <optimized out>
        action = <optimized out>
        ret = <optimized out>
        len = <optimized out>
        pparams = 0x48b2a0 <caldav_params>
        r = <optimized out>
        params = 0x48b2a0 <caldav_params>
        post_count = 0
        __s2_len = <optimized out>
        __s2 = <optimized out>
        txn = 0x7fffab891630
        r = <optimized out>
        ret = <optimized out>
        hdr = <optimized out>
        pparams = <optimized out>
        action = <optimized out>
        len = <optimized out>
        __s1_len = <optimized out>
        __result = <optimized out>
        pparams = 0x48b2a0 <caldav_params>
        action = <optimized out>
        r = <optimized out>
        ret = <optimized out>
        len = <optimized out>
        post_count = 0
        hdr = <optimized out>
        __s1_len = <optimized out>
        __s2_len = <optimized out>
        __s2 = <optimized out>
        __result = <optimized out>

#4  0x000000000045ce9d in cmdloop (conn=conn@entry=0x7fffab8957b0) at imap/httpd.c:2106
        meth_t = <optimized out>
        ret = <optimized out>
        __s1_len = <optimized out>
        req_line = 0x7fffab8925f8
        __s2_len = <optimized out>
        __s2 = <optimized out>
        __result = <optimized out>
        empty = 0
        txn = {
          conn = 0x7fffab8957b0, 
          http2 = {
            stream_id = 0, 
            num_resp_hdrs = 0, 
            resp_hdrs = {{
                name = 0x0, 
                value = 0x0, 
                namelen = 0, 
                valuelen = 0, 
                flags = 0 '\000'
              } <repeats 100 times>}
          }, 
          meth = 12, 
          flags = {
            ver = 1, 
            conn = 2, 
            upgrade = 2, 
            override = 0, 
            cors = 0, 
            mime = 0, 
            te = 0, 
            cc = 2, 
            ranges = 0, 
            vary = 10, 
            trailer = 0
          }, 
          req_line = {
            buf = "POST\000/dav/calendars/user/X/Default/\000HTTP/1.1", '\000' <repeats 7945 times>, 
            meth = 0x7fffab8925f8 "POST", 
            uri = 0x7fffab8925fd "/dav/calendars/user/X/Default/", 
            ver = 0x7fffab892627 "HTTP/1.1"
          }, 
          req_uri = 0x1f00e40, 
          req_tgt = {
            path = "/dav/calendars/user/X/Default/", '\000' <repeats 4055 times>, 
            tail = 0x7fffab894589 "", 
            namespace = 0x48b100 <namespace_calendar>, 
            userid = 0x1f37460 "X", 
            collection = 0x7fffab894581 "Default/", 
            collen = 7, 
            resource = 0x7fffab894589 "", 
            reslen = 0, 
            flags = 0, 
            allow = 2034455, 
            mbentry = 0x1f376e0, 
            mboxprefix = 0x7ffa9bb1461a "#calendars"
          }, 
          req_qparams = {
            size = 10, 
            table = 0x1f38d00, 
            pool = 0x1f374a0
          }, 
          req_hdrs = 0x1ef7990, 
          req_body = {
            flags = 40 '(', 
            framing = 2 '\002', 
            te = 0 '\000', 
            max = 2147483647, 
            len = 0, 
            payload = {
              s = 0x1f391c0 "\nBEGIN:VCALENDAR\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Ximian//NONSGML Test Call//EN\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\nDTSTAMP:20170528T094321Z\r\nDTSTART;VALUE=DATE:20180528
\r\nSUMMARY:A BC\r\nSTATUS:CONFIRMED\r\nTRANSP:TRANSPARENT\r\nRECURRENCE-ID;VALUE=DATE:20180528\r\nDTEND;VALUE=DATE:20180529\r\nRRULE:FREQ=YEARLY\r\nCLASS:PUBLIC\r\nSEQUENCE:1\r\nCREATED:20180506T183417Z\r\nLAS
T-MODIFIED:20180506T183417Z\r\nEND:VEVENT\r\nEND:VCALENDAR\r", 
              len = 413, 
              alloc = 512, 
              flags = 0
            }
          }, 
          auth_chal = {
            scheme = 0x48d000 <auth_schemes>, 
            param = 0x0
          }, 
          location = 0x0, 
          error = {
            desc = 0x0, 
            precond = 0, 
            node = 0x0, 
            resource = 0x0, 
            rights = 0
          }, 
          resp_body = {
            len = 0, 
            range = 0x0, 
            fname = 0x0, 
            enc = 0 '\000', 
            lang = 0x0, 
            loc = 0x0, 
            md5 = 0x0, 
            type = 0x0, 
            patch = 0x0, 
            prefs = 0, 
            link = 0x0, 
            lock = 0x0, 
            ctag = 0x0, 
            etag = 0x0, 
            lastmod = 0, 
            maxage = 0, 
            stag = 0x0, 
            cmid = 0x0, 
            iserial = 0, 
            payload = {
              s = 0x0, 
              len = 0, 
              alloc = 0, 
              flags = 0
            }
          }, 
          zbuf = {
            s = 0x0, 
            len = 0, 
            alloc = 0, 
            flags = 0
          }, 
          buf = {
            s = 0x1ef8240 "1454443908-506-143", 
            len = 0, 
            alloc = 2048, 
            flags = 0
          }
        }

#5  0x000000000045d470 in service_main (argc=<optimized out>, argv=<optimized out>, envp=envp@entry=0x7fffab897278)
    at imap/httpd.c:1116
        secprops = <optimized out>
        mechlist = 0x1ef7a00 "GSS-SPNEGO"
        mech = <optimized out>
        mechcount = -1
        mechlen = <optimized out>
        scheme = <optimized out>
        http_conn = {
          pin = 0x1ef3470, 
          pout = 0x1ef4590, 
          zstrm = 0x0, 
          brotli = 0x0, 
          http2_session = 0x0, 
          http2_options = 0x0
        }

#6  0x0000000000413c93 in main (argc=<optimized out>, argv=<optimized out>, envp=0x7fffab897278)
    at master/service.c:634
        fdflags = <optimized out>
        fd = <optimized out>
        p = <optimized out>
        service = <optimized out>
        request = <optimized out>
        opt = -1
        alt_config = <optimized out>
        call_debugger = <optimized out>
        debug_stdio = 0
        max_use = 250
        reuse_timeout = 104
        soctype = 1
        typelen = 4
        socname = {
          sa_family = 2, 
          sa_data = "\000P\177\000\000\003\000\000\000\000\000\000\000"
        }
        addrlen = 16
        id = <optimized out>
        path = "/usr/local/libexec/httpd", '\000' <repeats 3104 times>, [MANUALLY TRUNCATED FOR BREVITY]
        sbuf = {
          st_dev = 65025, 
          st_ino = 27264731, 
          st_nlink = 1, 
          st_mode = 33261, 
          st_uid = 0, 
          st_gid = 50, 
          __pad0 = 0, 
          st_rdev = 0, 
          st_size = 1591920, 
          st_blksize = 4096, 
          st_blocks = 3112, 
          st_atim = {
            tv_sec = 1525671372, 
            tv_nsec = 623150987
          }, 
          st_mtim = {
            tv_sec = 1525671253, 
            tv_nsec = 215411211
          }, 
          st_ctim = {
            tv_sec = 1525671253, 
            tv_nsec = 215411211
          }, 
          __glibc_reserved = {0, 0, 0}
        }
        start_ino = 27264731
        start_size = 1591920
        start_mtime = 1525671253
        service_argv = {
          count = 1, 
          alloc = 16, 
          data = 0x1ec9c60
        }
elliefm commented 6 years ago

@ksmurchison, looks like this crash is occurring in here:

https://github.com/cyrusimap/cyrus-imapd/blob/7854ec6f358db6bc1e433fb03ed916b37919196f/imap/http_caldav.c#L2920-L2925

Note that icalcomponent_get_uid() can return NULL if it didn't find such a property, so the crash occurs in the snprintf when we try to strhash(uid).

This function is quite different on master, but looks like it might be susceptible to a similar crash:

https://github.com/cyrusimap/cyrus-imapd/blob/d00d0293b607db2b7c51e02b0d5e03f44c808f02/imap/http_caldav.c#L3422-L3423

Note that hash_lookup(uid, ...) will also crash out (hilariously, yet unrelatedly, also in strhash(uid))

What should caldav_import() be doing when no UID is found? Throw it out? Generate one?

It looks like both versions of caldav_import rely entirely on icalrestriction_check() for validation. Is the failure to detect the missing UID a bug in that? (Though, even if it is, it'd be nice for Cyrus to throw back a good error instead of crashing out.)

elliefm commented 6 years ago

Looks like 2.5 is not affected by this: it threw a HTTP_BAD_REQUEST error when no uid was found:

https://github.com/cyrusimap/cyrus-imapd/blob/c805d3e834c9ba692fc10c34b0c4c973726465f2/imap/http_caldav.c#L2518-L2531

elliefm commented 6 years ago

I'm looking at the current libical source on our fork, and if I'm reading it correctly, it looks like it should detect a missing uid as invalid.

@dilyanpalauzov, which version of libical do you have there?

dilyanpalauzov commented 6 years ago

Providing that the POST is supposed to create an URL for the object and it is common that the UID is part of the address of the object, I was expecting that the server would generate the UID.

dilyanpalauzov commented 6 years ago

I use the latest libical v3.0.3-52-ga579ed50.

In imap/http_caldav.c:caldav_import() get_icalcomponent_errstr(ical) returns NULL both before and after calling icalrestrction_check(ical). The latter returns 1.

In libical/src/libical/icalrestrction.c:icalrestrction_check() method_prop is 0, so method = ICAL_METHOD_NONE.

static const icalrestriction_property_record icalrestriction_property_record contains for ICAL_METHOD_NONE and ICAL_UID_PROPERTY:

    {ICAL_METHOD_NONE, ICAL_VCALENDAR_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ZEROORONE, NULL},
    {ICAL_METHOD_NONE, ICAL_VEVENT_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ZEROORONE, NULL},
    {ICAL_METHOD_NONE, ICAL_VTODO_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ZEROORONE, NULL},
    {ICAL_METHOD_NONE, ICAL_VJOURNAL_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ZEROORONE, NULL},
    {ICAL_METHOD_NONE, ICAL_VFREEBUSY_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ONE, NULL},
    {ICAL_METHOD_NONE, ICAL_VTIMEZONE_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ZERO, NULL},
    {ICAL_METHOD_NONE, ICAL_XSTANDARD_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ZERO, NULL},
    {ICAL_METHOD_NONE, ICAL_XDAYLIGHT_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ZERO, NULL},
    {ICAL_METHOD_NONE, ICAL_XAUDIOALARM_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ZERO, NULL},
    {ICAL_METHOD_NONE, ICAL_XDISPLAYALARM_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ZERO, NULL},
    {ICAL_METHOD_NONE, ICAL_XEMAILALARM_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ZERO, NULL},
    {ICAL_METHOD_NONE, ICAL_XPROCEDUREALARM_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ZERO, NULL},
    {ICAL_METHOD_NONE, ICAL_VAVAILABILITY_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ONE, NULL},
    {ICAL_METHOD_NONE, ICAL_XAVAILABLE_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ONE, NULL},
    {ICAL_METHOD_NONE, ICAL_VPOLL_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ONE, NULL},
    {ICAL_METHOD_NONE, ICAL_VPATCH_COMPONENT, ICAL_UID_PROPERTY, ICAL_RESTRICTION_ONE, NULL},

in particular ICAL_VCALENDAR_COMPONENT + ICAL_UID_PROPERTY => ICAL_RESTRICTION_ZEROORONE.

ksmurchison commented 6 years ago

UID is s required property for a VEVENT per both RFC 5545 and 5546. The fact that icalrestriction_check() and Cyrus fail to detect that is a bug in both. Such a VEVENT should be rejected. In the case of bulk import this would result in a element containing an error.