diff --git a/ext/fileinfo/libmagic/cdf.c b/ext/fileinfo/libmagic/cdf.c index e1eb7b9bd13c..6b245158b101 100644 --- a/ext/fileinfo/libmagic/cdf.c +++ b/ext/fileinfo/libmagic/cdf.c @@ -1236,6 +1236,9 @@ cdf_unpack_catalog(const cdf_header_t *h, const cdf_stream_t *sst, extract_catalog_field(uint64_t, ce_timestamp, 8); reclen = cep->ce_namlen; + if (reclen == 0) + break; + if (reclen < 14) { cep->ce_namlen = 0; continue; diff --git a/ext/fileinfo/libmagic/readcdf.c b/ext/fileinfo/libmagic/readcdf.c index 6d6e3bc492b5..2e67badac76f 100644 --- a/ext/fileinfo/libmagic/readcdf.c +++ b/ext/fileinfo/libmagic/readcdf.c @@ -386,7 +386,7 @@ cdf_check_summary_info(struct magic_set *ms, const cdf_info_t *info, int i; const char *str = NULL; cdf_directory_t *d; - char name[__arraycount(d->d_name)]; + char name[__arraycount(d->d_name) + 1]; size_t j, k; #ifdef CDF_DEBUG @@ -400,8 +400,9 @@ cdf_check_summary_info(struct magic_set *ms, const cdf_info_t *info, return i; for (j = 0; str == NULL && j < dir->dir_len; j++) { d = &dir->dir_tab[j]; - for (k = 0; k < sizeof(name); k++) + for (k = 0; k < sizeof(name) - 1; k++) name[k] = CAST(char, cdf_tole2(d->d_name[k])); + name[sizeof(name) - 1] = '\0'; str = cdf_app_to_mime(name, NOTMIME(ms) ? name2desc : name2mime); } diff --git a/ext/fileinfo/tests/cdf_directory_name_bounds.phpt b/ext/fileinfo/tests/cdf_directory_name_bounds.phpt new file mode 100644 index 000000000000..8e68b7b977d4 --- /dev/null +++ b/ext/fileinfo/tests/cdf_directory_name_bounds.phpt @@ -0,0 +1,149 @@ +--TEST-- +fileinfo CDF: NUL-terminate directory names and stop on zero-length catalog records +--EXTENSIONS-- +fileinfo +--SKIPIF-- + +--FILE-- + pack('v', $v); + $u32 = fn(int $v): string => pack('V', $v); + $utf16le = static function (string $s): string { + $out = ''; + foreach (str_split($s) as $c) { + $out .= pack('v', ord($c)); + } + return $out . "\x00\x00"; + }; + + $dirEntry = function (string $name, int $type, int $firstSector, + int $size, bool $root = false) use ($u16, $u32, $utf16le): string { + $raw = $utf16le($name); + $e = str_repeat("\0", 128); + $e = substr_replace($e, $raw, 0, strlen($raw)); + $e = substr_replace($e, $u16(strlen($raw)), 64, 2); + $e[66] = chr($type); + $e[67] = chr(1); + $e = substr_replace($e, pack('i', -1), 68, 4); + $e = substr_replace($e, pack('i', -1), 72, 4); + $e = substr_replace($e, pack('i', $root ? 1 : -1), 76, 4); + $e = substr_replace($e, $u32($firstSector), 116, 4); + $e = substr_replace($e, $u32($size), 120, 4); + return $e; + }; + + // SummaryInformation: valid summary header, one CDF_NULL property. + $summary = str_repeat("\0", 128); + $summary = substr_replace($summary, $u16(0xFFFE), 0, 2); + $summary = substr_replace($summary, $u16(0x0602), 4, 2); + $summary = substr_replace($summary, $u16(2), 6, 2); + $summary = substr_replace($summary, $u32(1), 24, 4); // si_count + $summary = substr_replace($summary, $u32(0x30), 0x1c + 16, 4); // sd_offset + $summary = substr_replace($summary, $u32(0x18), 0x30, 4); // sh_len + $summary = substr_replace($summary, $u32(1), 0x34, 4); // sh_properties + $summary = substr_replace($summary, $u32(1), 0x38, 4); // pi_id + $summary = substr_replace($summary, $u32(0x10), 0x3c, 4); // offset + $summary = substr_replace($summary, $u32(1), 0x40, 4); // CDF_NULL + $summary = substr($summary, 0, 68); + + // Catalog: two 2-byte records followed by a zero-length record. + $catalog = "\x02\x00\x02\x00\x00\x00"; + + $miniStream = str_repeat("\0", $sector); + $miniStream = substr_replace($miniStream, $summary, 0, strlen($summary)); + $miniStream = substr_replace($miniStream, $catalog, 2 * $mini, strlen($catalog)); + + $miniFat = array_fill(0, $sector / 4, $FREE); + $miniFat[0] = 1; // summary chain: mini sector 0 -> 1 + $miniFat[1] = $ENDOFCHAIN; + $miniFat[2] = $ENDOFCHAIN; // catalog: single mini sector + + $root = $dirEntry('Root Entry', 5, 3, $sector, true); + $cat = $dirEntry('Catalog', 2, 2, strlen($catalog)); + $summ = $dirEntry("\x05SummaryInformation", 2, 0, strlen($summary)); + if ($unterminatedName) { + // 32 nonzero UTF-16 units (0x0041): no NUL anywhere in d_name. + $fourth = str_repeat("\0", 128); + $rawName = str_repeat("\x41\x00", 32); + $fourth = substr_replace($fourth, $rawName, 0, 64); + $fourth = substr_replace($fourth, $u16(64), 64, 2); + $fourth[66] = chr(2); + $fourth[67] = chr(1); + $fourth = substr_replace($fourth, pack('i', -1), 68, 4); + $fourth = substr_replace($fourth, pack('i', -1), 72, 4); + $fourth = substr_replace($fourth, pack('i', -1), 76, 4); + } else { + $fourth = str_repeat("\0", 128); + } + $directory = $root . $cat . $summ . $fourth; + + $fat = array_fill(0, $sector / 4, $FREE); + $fat[0] = $FATSECT; + $fat[1] = $ENDOFCHAIN; + $fat[2] = $ENDOFCHAIN; + $fat[3] = $ENDOFCHAIN; + + $h = str_repeat("\0", 512); + $h = substr_replace($h, hex2bin('D0CF11E0A1B11AE1'), 0, 8); + $h = substr_replace($h, $u16(0x003E), 24, 2); + $h = substr_replace($h, $u16(0x0003), 26, 2); + $h = substr_replace($h, $u16(0xFFFE), 28, 2); + $h = substr_replace($h, $u16(9), 30, 2); + $h = substr_replace($h, $u16(6), 32, 2); + $h = substr_replace($h, $u32(1), 44, 4); // FAT sectors + $h = substr_replace($h, $u32(2), 48, 4); // first directory + $h = substr_replace($h, $u32(4096), 56, 4); // mini cutoff + $h = substr_replace($h, $u32(1), 60, 4); // first miniFAT + $h = substr_replace($h, $u32(1), 64, 4); // miniFAT sectors + $h = substr_replace($h, $u32($ENDOFCHAIN), 68, 4); // first DIFAT + $h = substr_replace($h, $u32(0), 72, 4); // DIFAT count + $h = substr_replace($h, $u32(0), 76, 4); // DIFAT[0] + for ($k = 1; $k < 109; $k++) { + $h = substr_replace($h, $u32($FREE), 76 + 4 * $k, 4); + } + + $out = $h; + foreach ($fat as $v) { + $out .= $u32($v); + } + foreach ($miniFat as $v) { + $out .= $u32($v); + } + $out .= $directory; + $out .= $miniStream; + return $out; +} + +$finfo = finfo_open(FILEINFO_MIME_TYPE); +foreach ([true, false] as $unterminated) { + $mime = finfo_buffer($finfo, build_cdf($unterminated)); + var_dump(is_string($mime) && str_contains($mime, 'ms-office')); +} +?> +--EXPECT-- +bool(true) +bool(true) diff --git a/ext/snmp/snmp.c b/ext/snmp/snmp.c index 0c0607392366..ac1275e549e8 100644 --- a/ext/snmp/snmp.c +++ b/ext/snmp/snmp.c @@ -263,8 +263,11 @@ static void php_snmp_getvalue(struct variable_list *vars, zval *snmpval, int val php_error_docref(NULL, E_WARNING, "snprint_value() asks for a buffer more than 512k, Net-SNMP bug?"); break; } - /* buffer is not long enough to hold full output, double it */ - val_len *= 2; + /* buffer is not long enough to hold full output, double it. + * A zero-length value (e.g. ASN_NULL) never grows by doubling, + * so make sure the buffer at least doubles from the current + * buffer size to guarantee forward progress. */ + val_len = MAX(val_len * 2, buflen * 2); } else { break; } diff --git a/ext/snmp/tests/snmp_zero_length_value.phpt b/ext/snmp/tests/snmp_zero_length_value.phpt new file mode 100644 index 000000000000..a8667eb6dbba --- /dev/null +++ b/ext/snmp/tests/snmp_zero_length_value.phpt @@ -0,0 +1,98 @@ +--TEST-- +SNMP: php_snmp_getvalue() handles zero-length varbind values with long OIDs +--EXTENSIONS-- +snmp +--SKIPIF-- + +--FILE-- +>= 8 + return bytes([0x80 | len(o)]) + bytes(o) +def tlv(t, v): return bytes([t]) + enc_len(len(v)) + v +def enc_int(n): + if n == 0: body = b"\x00" + else: + o = [] + while n: o.insert(0, n & 0xFF); n >>= 8 + if o[0] & 0x80: o.insert(0, 0) + body = bytes(o) + return tlv(2, body) +def enc_oid(s): + vals = [40*s[0] + s[1]] + s[2:] + out = b"" + for v in vals: + c = [v & 0x7F]; v >>= 7 + while v: c.append((v & 0x7F) | 0x80); v >>= 7 + out += bytes(reversed(c)) + return tlv(6, out) +def walk(b, i): + tag = b[i]; i += 1 + l = b[i]; i += 1 + if l & 0x80: + k = l & 0x7F + l = int.from_bytes(b[i:i+k], "big"); i += k + return tag, i, i + l +def req_id(d): + _, i, _ = walk(d, 0) + _, _, i = walk(d, i) + _, _, i = walk(d, i) + _, i, _ = walk(d, i) + _, s, e = walk(d, i) + return int.from_bytes(d[s:e], "big", signed=True) +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +sock.bind(("127.0.0.1", 0)) +print(sock.getsockname()[1], flush=True) +pkt, addr = sock.recvfrom(4096) +rid = req_id(pkt) +subids = [1, 3] + [4294967295] * (N - 2) +varbind = tlv(0x30, enc_oid(subids) + tlv(5, b"")) # long OID + ASN_NULL (05 00) +pdu = tlv(0xA2, enc_int(rid) + enc_int(0) + enc_int(0) + tlv(0x30, varbind)) +sock.sendto(tlv(0x30, enc_int(1) + tlv(4, b"public") + pdu), addr) +PY; + +$spec = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], +]; +$proc = proc_open([$python, '-c', $agent], $spec, $pipes); +if (!is_resource($proc)) { + die('cannot start test agent'); +} + +// The agent binds to a free port (port 0) and reports it on stdout. +$port = (int) trim(fgets($pipes[1])); +if ($port <= 0 || $port > 65535) { + proc_terminate($proc); + die('cannot determine test agent port'); +} + +// Default valueretrieval (SNMP_VALUE_LIBRARY), short timeout/retries. +// Before the fix this call never returns; with the fix it returns the +// rendered NULL value as a string. +$value = snmpget("127.0.0.1:$port", 'public', '1.3.6.1.2.1.1.1.0', 1, 1); +var_dump(is_string($value)); + +proc_terminate($proc); +proc_close($proc); +?> +--EXPECT-- +bool(true) diff --git a/ext/soap/php_sdl.c b/ext/soap/php_sdl.c index 69ab56169c40..77742e863823 100644 --- a/ext/soap/php_sdl.c +++ b/ext/soap/php_sdl.c @@ -1321,11 +1321,52 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) #define WSDL_CACHE_VERSION 0x11 -#define WSDL_CACHE_GET(ret,type,buf) memcpy(&ret,*buf,sizeof(type)); *buf += sizeof(type); -#define WSDL_CACHE_GET_INT(ret,buf) ret = ((unsigned char)(*buf)[0])|((unsigned char)(*buf)[1]<<8)|((unsigned char)(*buf)[2]<<16)|((unsigned)(*buf)[3]<<24); *buf += 4; -#define WSDL_CACHE_GET_1(ret,type,buf) ret = (type)(**buf); (*buf)++; -#define WSDL_CACHE_GET_N(ret,n,buf) memcpy(ret,*buf,n); *buf += n; -#define WSDL_CACHE_SKIP(n,buf) *buf += n; +/* + * The on-disk WSDL cache lives in a shared directory under a deterministic + * file name, so its contents must never be trusted. The reader below carries + * the current position, the end of the file buffer and a failure flag. Every + * primitive read is bounds-checked; once a failure is recorded all further + * reads become no-ops, so deserialization cannot touch memory outside of the + * buffer and get_sdl_from_cache() discards the partially built SDL. + */ +typedef struct _wsdl_cache_reader { + char *p; + char *end; + bool failed; + size_t max_type; /* highest valid index into the types table */ + size_t max_encoder; /* highest valid index into the encoders table */ +} wsdl_cache_reader; + +#define WSDL_CACHE_REMAINING(r) ((size_t)((r)->end - (r)->p)) +#define WSDL_CACHE_FAIL(r) do { (r)->failed = 1; (r)->p = (r)->end; } while (0) + +#define WSDL_CACHE_GET(ret,type,r) \ + do { \ + if (WSDL_CACHE_REMAINING(r) >= sizeof(type)) { \ + memcpy(&(ret),(r)->p,sizeof(type)); (r)->p += sizeof(type); \ + } else { (ret) = (type)0; WSDL_CACHE_FAIL(r); } \ + } while (0) +#define WSDL_CACHE_GET_INT(ret,r) \ + do { \ + if (WSDL_CACHE_REMAINING(r) >= 4) { \ + (ret) = ((unsigned char)(r)->p[0])|((unsigned char)(r)->p[1]<<8)|((unsigned char)(r)->p[2]<<16)|((unsigned)(r)->p[3]<<24); \ + (r)->p += 4; \ + } else { (ret) = 0; WSDL_CACHE_FAIL(r); } \ + } while (0) +#define WSDL_CACHE_GET_1(ret,type,r) \ + do { \ + if (WSDL_CACHE_REMAINING(r) >= 1) { (ret) = (type)(*(r)->p); (r)->p++; } \ + else { (ret) = (type)0; WSDL_CACHE_FAIL(r); } \ + } while (0) +#define WSDL_CACHE_SKIP(n,r) \ + do { \ + if (WSDL_CACHE_REMAINING(r) >= (size_t)(n)) { (r)->p += (n); } \ + else { WSDL_CACHE_FAIL(r); } \ + } while (0) +/* Inspect the next byte without consuming it. When the buffer is exhausted + * this evaluates to 0, which drives the caller down the "absent" branch whose + * subsequent SKIP/GET records the failure, so no byte is ever read past end. */ +#define WSDL_CACHE_PEEK_1(r) (WSDL_CACHE_REMAINING(r) >= 1 ? (r)->p[0] : 0) #define WSDL_CACHE_PUT_INT(val,buf) smart_str_appendc(buf,(char)(val & 0xff)); \ smart_str_appendc(buf,(char)((val >> 8) & 0xff)); \ @@ -1336,36 +1377,47 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) #define WSDL_NO_STRING_MARKER 0x7fffffff -static char* sdl_deserialize_string(char **in) +static char* sdl_deserialize_string(wsdl_cache_reader *in) { char *s; size_t len; WSDL_CACHE_GET_INT(len, in); - if (len == WSDL_NO_STRING_MARKER) { + if (in->failed || len == WSDL_NO_STRING_MARKER) { return NULL; - } else { - s = emalloc(len+1); - WSDL_CACHE_GET_N(s, len, in); - s[len] = '\0'; - return s; } + if (UNEXPECTED(len > WSDL_CACHE_REMAINING(in))) { + WSDL_CACHE_FAIL(in); + return NULL; + } + s = emalloc(len+1); + memcpy(s, in->p, len); + in->p += len; + s[len] = '\0'; + return s; } -static void sdl_deserialize_key(HashTable* ht, void* data, char **in) +static void sdl_deserialize_key(HashTable* ht, void* data, wsdl_cache_reader *in) { size_t len; WSDL_CACHE_GET_INT(len, in); + if (in->failed) { + return; + } if (len == WSDL_NO_STRING_MARKER) { zend_hash_next_index_insert_ptr(ht, data); } else { - zend_hash_str_add_ptr(ht, *in, len, data); - WSDL_CACHE_SKIP(len, in); + if (UNEXPECTED(len > WSDL_CACHE_REMAINING(in))) { + WSDL_CACHE_FAIL(in); + return; + } + zend_hash_str_add_ptr(ht, in->p, len, data); + in->p += len; } } -static void sdl_deserialize_attribute(sdlAttributePtr attr, encodePtr *encoders, char **in) +static void sdl_deserialize_attribute(sdlAttributePtr attr, encodePtr *encoders, wsdl_cache_reader *in) { size_t i; @@ -1377,13 +1429,23 @@ static void sdl_deserialize_attribute(sdlAttributePtr attr, encodePtr *encoders, WSDL_CACHE_GET_1(attr->form, sdlForm, in); WSDL_CACHE_GET_1(attr->use, sdlUse, in); WSDL_CACHE_GET_INT(i, in); - attr->encode = encoders[i]; + if (i <= in->max_encoder) { + attr->encode = encoders[i]; + } else { + attr->encode = NULL; + WSDL_CACHE_FAIL(in); + } WSDL_CACHE_GET_INT(i, in); + if (UNEXPECTED(i > WSDL_CACHE_REMAINING(in))) { + WSDL_CACHE_FAIL(in); + i = 0; + } if (i > 0) { attr->extraAttributes = emalloc(sizeof(HashTable)); zend_hash_init(attr->extraAttributes, i, NULL, delete_extra_attribute, 0); while (i > 0) { sdlExtraAttributePtr x = emalloc(sizeof(sdlExtraAttribute)); + memset(x, 0, sizeof(sdlExtraAttribute)); sdl_deserialize_key(attr->extraAttributes, x, in); x->ns = sdl_deserialize_string(in); x->val = sdl_deserialize_string(in); @@ -1392,9 +1454,9 @@ static void sdl_deserialize_attribute(sdlAttributePtr attr, encodePtr *encoders, } } -static sdlRestrictionIntPtr sdl_deserialize_resriction_int(char **in) +static sdlRestrictionIntPtr sdl_deserialize_resriction_int(wsdl_cache_reader *in) { - if (**in == 1) { + if (WSDL_CACHE_PEEK_1(in) == 1) { sdlRestrictionIntPtr x = emalloc(sizeof(sdlRestrictionInt)); WSDL_CACHE_SKIP(1, in); WSDL_CACHE_GET_INT(x->value, in); @@ -1406,10 +1468,11 @@ static sdlRestrictionIntPtr sdl_deserialize_resriction_int(char **in) } } -static sdlRestrictionCharPtr sdl_deserialize_resriction_char(char **in) +static sdlRestrictionCharPtr sdl_deserialize_resriction_char(wsdl_cache_reader *in) { - if (**in == 1) { + if (WSDL_CACHE_PEEK_1(in) == 1) { sdlRestrictionCharPtr x = emalloc(sizeof(sdlRestrictionChar)); + memset(x, 0, sizeof(sdlRestrictionChar)); WSDL_CACHE_SKIP(1, in); x->value = sdl_deserialize_string(in); WSDL_CACHE_GET_1(x->fixed, char, in); @@ -1420,10 +1483,11 @@ static sdlRestrictionCharPtr sdl_deserialize_resriction_char(char **in) } } -static sdlContentModelPtr sdl_deserialize_model(sdlTypePtr *types, sdlTypePtr *elements, char **in) +static sdlContentModelPtr sdl_deserialize_model(sdlTypePtr *types, sdlTypePtr *elements, size_t elements_limit, wsdl_cache_reader *in) { size_t i; sdlContentModelPtr model = emalloc(sizeof(sdlContentModel)); + memset(model, 0, sizeof(sdlContentModel)); WSDL_CACHE_GET_1(model->kind, sdlContentKind, in); WSDL_CACHE_GET_INT(model->min_occurs, in); @@ -1431,16 +1495,25 @@ static sdlContentModelPtr sdl_deserialize_model(sdlTypePtr *types, sdlTypePtr *e switch (model->kind) { case XSD_CONTENT_ELEMENT: WSDL_CACHE_GET_INT(i, in); - model->u.element = elements[i]; + if (i <= elements_limit) { + model->u.element = elements[i]; + } else { + model->u.element = NULL; + WSDL_CACHE_FAIL(in); + } break; case XSD_CONTENT_SEQUENCE: case XSD_CONTENT_ALL: case XSD_CONTENT_CHOICE: WSDL_CACHE_GET_INT(i, in); + if (UNEXPECTED(i > WSDL_CACHE_REMAINING(in))) { + WSDL_CACHE_FAIL(in); + i = 0; + } model->u.content = emalloc(sizeof(HashTable)); zend_hash_init(model->u.content, i, NULL, delete_model, 0); while (i > 0) { - sdlContentModelPtr x = sdl_deserialize_model(types, elements, in); + sdlContentModelPtr x = sdl_deserialize_model(types, elements, elements_limit, in); zend_hash_next_index_insert_ptr(model->u.content, x); i--; } @@ -1450,7 +1523,12 @@ static sdlContentModelPtr sdl_deserialize_model(sdlTypePtr *types, sdlTypePtr *e break; case XSD_CONTENT_GROUP: WSDL_CACHE_GET_INT(i, in); - model->u.group = types[i]; + if (i <= in->max_type) { + model->u.group = types[i]; + } else { + model->u.group = NULL; + WSDL_CACHE_FAIL(in); + } break; default: break; @@ -1458,9 +1536,10 @@ static sdlContentModelPtr sdl_deserialize_model(sdlTypePtr *types, sdlTypePtr *e return model; } -static void sdl_deserialize_type(sdlTypePtr type, sdlTypePtr *types, encodePtr *encoders, char **in) +static void sdl_deserialize_type(sdlTypePtr type, sdlTypePtr *types, encodePtr *encoders, wsdl_cache_reader *in) { size_t i; + size_t elements_limit = 0; sdlTypePtr *elements = NULL; WSDL_CACHE_GET_1(type->kind, sdlTypeKind, in); @@ -1473,9 +1552,14 @@ static void sdl_deserialize_type(sdlTypePtr type, sdlTypePtr *types, encodePtr * WSDL_CACHE_GET_1(type->form, sdlForm, in); WSDL_CACHE_GET_INT(i, in); - type->encode = encoders[i]; + if (i <= in->max_encoder) { + type->encode = encoders[i]; + } else { + type->encode = NULL; + WSDL_CACHE_FAIL(in); + } - if (**in == 1) { + if (WSDL_CACHE_PEEK_1(in) == 1) { WSDL_CACHE_SKIP(1, in); type->restrictions = emalloc(sizeof(sdlRestrictions)); /*memset(type->restrictions, 0, sizeof(sdlRestrictions));*/ @@ -1491,6 +1575,10 @@ static void sdl_deserialize_type(sdlTypePtr type, sdlTypePtr *types, encodePtr * type->restrictions->whiteSpace = sdl_deserialize_resriction_char(in); type->restrictions->pattern = sdl_deserialize_resriction_char(in); WSDL_CACHE_GET_INT(i, in); + if (UNEXPECTED(i > WSDL_CACHE_REMAINING(in))) { + WSDL_CACHE_FAIL(in); + i = 0; + } if (i > 0) { type->restrictions->enumeration = emalloc(sizeof(HashTable)); zend_hash_init(type->restrictions->enumeration, i, NULL, delete_restriction_var_char, 0); @@ -1507,7 +1595,12 @@ static void sdl_deserialize_type(sdlTypePtr type, sdlTypePtr *types, encodePtr * } WSDL_CACHE_GET_INT(i, in); + if (UNEXPECTED(i > WSDL_CACHE_REMAINING(in))) { + WSDL_CACHE_FAIL(in); + i = 0; + } if (i > 0) { + elements_limit = i; elements = safe_emalloc((i+1), sizeof(sdlTypePtr), 0); elements[0] = NULL; type->elements = emalloc(sizeof(HashTable)); @@ -1523,6 +1616,10 @@ static void sdl_deserialize_type(sdlTypePtr type, sdlTypePtr *types, encodePtr * } WSDL_CACHE_GET_INT(i, in); + if (UNEXPECTED(i > WSDL_CACHE_REMAINING(in))) { + WSDL_CACHE_FAIL(in); + i = 0; + } if (i > 0) { type->attributes = emalloc(sizeof(HashTable)); zend_hash_init(type->attributes, i, NULL, delete_attribute, 0); @@ -1535,9 +1632,9 @@ static void sdl_deserialize_type(sdlTypePtr type, sdlTypePtr *types, encodePtr * } } - if (**in != 0) { + if (WSDL_CACHE_PEEK_1(in) != 0) { WSDL_CACHE_SKIP(1, in); - type->model = sdl_deserialize_model(types, elements, in); + type->model = sdl_deserialize_model(types, elements, elements_limit, in); } else { WSDL_CACHE_SKIP(1, in); } @@ -1546,7 +1643,7 @@ static void sdl_deserialize_type(sdlTypePtr type, sdlTypePtr *types, encodePtr * } } -static void sdl_deserialize_encoder(encodePtr enc, sdlTypePtr *types, char **in) +static void sdl_deserialize_encoder(encodePtr enc, sdlTypePtr *types, wsdl_cache_reader *in) { size_t i; @@ -1557,7 +1654,12 @@ static void sdl_deserialize_encoder(encodePtr enc, sdlTypePtr *types, char **in) enc->details.clark_notation = zend_strpprintf(0, "{%s}%s", enc->details.ns, enc->details.type_str); } WSDL_CACHE_GET_INT(i, in); - enc->details.sdl_type = types[i]; + if (i <= in->max_type) { + enc->details.sdl_type = types[i]; + } else { + enc->details.sdl_type = NULL; + WSDL_CACHE_FAIL(in); + } enc->to_xml = sdl_guess_convert_xml; enc->to_zval = sdl_guess_convert_zval; @@ -1591,7 +1693,7 @@ static void sdl_deserialize_encoder(encodePtr enc, sdlTypePtr *types, char **in) } } -static void sdl_deserialize_soap_body(sdlSoapBindingFunctionBodyPtr body, encodePtr *encoders, sdlTypePtr *types, char **in) +static void sdl_deserialize_soap_body(sdlSoapBindingFunctionBodyPtr body, encodePtr *encoders, sdlTypePtr *types, wsdl_cache_reader *in) { size_t i, n; @@ -1603,6 +1705,10 @@ static void sdl_deserialize_soap_body(sdlSoapBindingFunctionBodyPtr body, encode } body->ns = sdl_deserialize_string(in); WSDL_CACHE_GET_INT(i, in); + if (UNEXPECTED(i > WSDL_CACHE_REMAINING(in))) { + WSDL_CACHE_FAIL(in); + i = 0; + } if (i > 0) { body->headers = emalloc(sizeof(HashTable)); zend_hash_init(body->headers, i, NULL, delete_header, 0); @@ -1619,13 +1725,27 @@ static void sdl_deserialize_soap_body(sdlSoapBindingFunctionBodyPtr body, encode tmp->name = sdl_deserialize_string(in); tmp->ns = sdl_deserialize_string(in); WSDL_CACHE_GET_INT(n, in); - tmp->encode = encoders[n]; + if (n <= in->max_encoder) { + tmp->encode = encoders[n]; + } else { + tmp->encode = NULL; + WSDL_CACHE_FAIL(in); + } WSDL_CACHE_GET_INT(n, in); - tmp->element = types[n]; + if (n <= in->max_type) { + tmp->element = types[n]; + } else { + tmp->element = NULL; + WSDL_CACHE_FAIL(in); + } --i; size_t j; WSDL_CACHE_GET_INT(j, in); + if (UNEXPECTED(j > WSDL_CACHE_REMAINING(in))) { + WSDL_CACHE_FAIL(in); + j = 0; + } if (j > 0) { tmp->headerfaults = emalloc(sizeof(HashTable)); zend_hash_init(tmp->headerfaults, j, NULL, delete_header, 0); @@ -1643,9 +1763,19 @@ static void sdl_deserialize_soap_body(sdlSoapBindingFunctionBodyPtr body, encode tmp2->ns = sdl_deserialize_string(in); WSDL_CACHE_GET_INT(n, in); - tmp2->encode = encoders[n]; + if (n <= in->max_encoder) { + tmp2->encode = encoders[n]; + } else { + tmp2->encode = NULL; + WSDL_CACHE_FAIL(in); + } WSDL_CACHE_GET_INT(n, in); - tmp2->element = types[n]; + if (n <= in->max_type) { + tmp2->element = types[n]; + } else { + tmp2->element = NULL; + WSDL_CACHE_FAIL(in); + } --j; } } @@ -1653,24 +1783,39 @@ static void sdl_deserialize_soap_body(sdlSoapBindingFunctionBodyPtr body, encode } } -static HashTable* sdl_deserialize_parameters(encodePtr *encoders, sdlTypePtr *types, char **in) +static HashTable* sdl_deserialize_parameters(encodePtr *encoders, sdlTypePtr *types, wsdl_cache_reader *in) { size_t i, n; HashTable *ht; WSDL_CACHE_GET_INT(i, in); if (i == 0) {return NULL;} + if (UNEXPECTED(i > WSDL_CACHE_REMAINING(in))) { + WSDL_CACHE_FAIL(in); + return NULL; + } ht = emalloc(sizeof(HashTable)); zend_hash_init(ht, i, NULL, delete_parameter, 0); while (i > 0) { sdlParamPtr param = emalloc(sizeof(sdlParam)); + memset(param, 0, sizeof(sdlParam)); sdl_deserialize_key(ht, param, in); param->paramName = sdl_deserialize_string(in); WSDL_CACHE_GET_INT(param->order, in); WSDL_CACHE_GET_INT(n, in); - param->encode = encoders[n]; + if (n <= in->max_encoder) { + param->encode = encoders[n]; + } else { + param->encode = NULL; + WSDL_CACHE_FAIL(in); + } WSDL_CACHE_GET_INT(n, in); - param->element = types[n]; + if (n <= in->max_type) { + param->element = types[n]; + } else { + param->element = NULL; + WSDL_CACHE_FAIL(in); + } --i; } return ht; @@ -1678,18 +1823,18 @@ static HashTable* sdl_deserialize_parameters(encodePtr *encoders, sdlTypePtr *ty static sdlPtr get_sdl_from_cache(const char *fn, const char *uri, size_t uri_len, time_t t, time_t *cached) { - sdlPtr sdl; + sdlPtr sdl = NULL; time_t old_t; int i, num_groups, num_types, num_elements, num_encoders, num_bindings, num_func; sdlFunctionPtr *functions = NULL; - sdlBindingPtr *bindings; - sdlTypePtr *types; - encodePtr *encoders; + sdlBindingPtr *bindings = NULL; + sdlTypePtr *types = NULL; + encodePtr *encoders = NULL; const encode *enc; int f; struct stat st; - char *in, *buf; + char *buf; f = open(fn, O_RDONLY|O_BINARY); if (UNEXPECTED(f < 0)) { @@ -1699,56 +1844,71 @@ static sdlPtr get_sdl_from_cache(const char *fn, const char *uri, size_t uri_len close(f); return NULL; } - buf = in = emalloc(st.st_size); - if (UNEXPECTED(read(f, in, st.st_size) != st.st_size)) { + if (UNEXPECTED(st.st_size < 6)) { close(f); - efree(in); + return NULL; + } + /* Extra zeroed padding so that a clamped reader never reads past the + * allocation when a truncated file is rejected. */ + buf = ecalloc((size_t)st.st_size + 16, 1); + if (UNEXPECTED(read(f, buf, st.st_size) != st.st_size)) { + close(f); + efree(buf); return NULL; } close(f); - if (UNEXPECTED(strncmp(in,"wsdl",4) != 0 || in[4] != WSDL_CACHE_VERSION || in[5] != '\0')) { + if (UNEXPECTED(strncmp(buf,"wsdl",4) != 0 || buf[4] != WSDL_CACHE_VERSION || buf[5] != '\0')) { unlink(fn); efree(buf); return NULL; } - in += 6; - WSDL_CACHE_GET(old_t, time_t, &in); - if (old_t < t) { + wsdl_cache_reader reader; + reader.p = buf + 6; + reader.end = buf + st.st_size; + reader.failed = 0; + reader.max_type = reader.max_encoder = 0; + wsdl_cache_reader *in = &reader; + + WSDL_CACHE_GET(old_t, time_t, in); + if (in->failed || old_t < t) { unlink(fn); efree(buf); return NULL; } *cached = old_t; - WSDL_CACHE_GET_INT(i, &in); - if (i != uri_len || strncmp(in, uri, i) != 0) { + WSDL_CACHE_GET_INT(i, in); + if (in->failed || i < 0 || (size_t)i != uri_len || WSDL_CACHE_REMAINING(in) < (size_t)i || strncmp(in->p, uri, i) != 0) { unlink(fn); efree(buf); return NULL; } - WSDL_CACHE_SKIP(i, &in); + WSDL_CACHE_SKIP(i, in); sdl = emalloc(sizeof(*sdl)); memset(sdl, 0, sizeof(*sdl)); + /* Initialized up front so that any goto invalid_cache below can free + * the partially built SDL through delete_sdl() safely. */ + zend_hash_init(&sdl->functions, 0, NULL, delete_function, 0); - sdl->source = sdl_deserialize_string(&in); - sdl->target_ns = sdl_deserialize_string(&in); + sdl->source = sdl_deserialize_string(in); + sdl->target_ns = sdl_deserialize_string(in); - WSDL_CACHE_GET_INT(num_groups, &in); - WSDL_CACHE_GET_INT(num_types, &in); - WSDL_CACHE_GET_INT(num_elements, &in); - WSDL_CACHE_GET_INT(num_encoders, &in); + WSDL_CACHE_GET_INT(num_groups, in); + WSDL_CACHE_GET_INT(num_types, in); + WSDL_CACHE_GET_INT(num_elements, in); + WSDL_CACHE_GET_INT(num_encoders, in); + if (in->failed || num_groups < 0 || num_types < 0 || num_elements < 0 || num_encoders < 0 + || (size_t)num_groups + (size_t)num_types + (size_t)num_elements > (size_t)st.st_size + || (size_t)num_encoders > (size_t)st.st_size) { + goto invalid_cache; + } i = num_groups+num_types+num_elements; types = safe_emalloc((i+1), sizeof(sdlTypePtr), 0); - types[0] = NULL; - while (i > 0) { - types[i] = emalloc(sizeof(sdlType)); - memset(types[i], 0, sizeof(sdlType)); - i--; - } + memset(types, 0, (i+1) * sizeof(sdlTypePtr)); i = num_encoders; enc = defaultEncoding; @@ -1756,26 +1916,31 @@ static sdlPtr get_sdl_from_cache(const char *fn, const char *uri, size_t uri_len i++; enc++; } encoders = safe_emalloc((i+1), sizeof(encodePtr), 0); - i = num_encoders; - encoders[0] = NULL; - while (i > 0) { - encoders[i] = emalloc(sizeof(encode)); - memset(encoders[i], 0, sizeof(encode)); - i--; - } + memset(encoders, 0, (i+1) * sizeof(encodePtr)); i = num_encoders; enc = defaultEncoding; while (enc->details.type != END_KNOWN_TYPES) { encoders[++i] = (encodePtr)enc++; } + /* File supplied indices may only address slots that actually exist. + * The encoder table holds the deserialized custom encoders followed by + * the built-in default encoders, and serialization references both kinds + * by index, so the upper bound is the final index reached after the + * default encoders were appended above (num_encoders + number of + * defaults), not merely num_encoders. */ + reader.max_type = num_groups + num_types + num_elements; + reader.max_encoder = (size_t)i; + i = 1; if (num_groups > 0) { sdl->groups = emalloc(sizeof(HashTable)); zend_hash_init(sdl->groups, num_groups, NULL, delete_type, 0); while (i < num_groups+1) { - sdl_deserialize_key(sdl->groups, types[i], &in); - sdl_deserialize_type(types[i], types, encoders, &in); + types[i] = emalloc(sizeof(sdlType)); + memset(types[i], 0, sizeof(sdlType)); + sdl_deserialize_key(sdl->groups, types[i], in); + sdl_deserialize_type(types[i], types, encoders, in); i++; } } @@ -1784,8 +1949,10 @@ static sdlPtr get_sdl_from_cache(const char *fn, const char *uri, size_t uri_len sdl->types = emalloc(sizeof(HashTable)); zend_hash_init(sdl->types, num_types, NULL, delete_type, 0); while (i < num_groups+num_types+1) { - sdl_deserialize_key(sdl->types, types[i], &in); - sdl_deserialize_type(types[i], types, encoders, &in); + types[i] = emalloc(sizeof(sdlType)); + memset(types[i], 0, sizeof(sdlType)); + sdl_deserialize_key(sdl->types, types[i], in); + sdl_deserialize_type(types[i], types, encoders, in); i++; } } @@ -1794,8 +1961,10 @@ static sdlPtr get_sdl_from_cache(const char *fn, const char *uri, size_t uri_len sdl->elements = emalloc(sizeof(HashTable)); zend_hash_init(sdl->elements, num_elements, NULL, delete_type, 0); while (i < num_groups+num_types+num_elements+1) { - sdl_deserialize_key(sdl->elements, types[i], &in); - sdl_deserialize_type(types[i], types, encoders, &in); + types[i] = emalloc(sizeof(sdlType)); + memset(types[i], 0, sizeof(sdlType)); + sdl_deserialize_key(sdl->elements, types[i], in); + sdl_deserialize_type(types[i], types, encoders, in); i++; } } @@ -1805,71 +1974,89 @@ static sdlPtr get_sdl_from_cache(const char *fn, const char *uri, size_t uri_len sdl->encoders = emalloc(sizeof(HashTable)); zend_hash_init(sdl->encoders, num_encoders, NULL, delete_encoder, 0); while (i < num_encoders+1) { - sdl_deserialize_key(sdl->encoders, encoders[i], &in); - sdl_deserialize_encoder(encoders[i], types, &in); + encoders[i] = emalloc(sizeof(encode)); + memset(encoders[i], 0, sizeof(encode)); + sdl_deserialize_key(sdl->encoders, encoders[i], in); + sdl_deserialize_encoder(encoders[i], types, in); i++; } } /* deserialize bindings */ - WSDL_CACHE_GET_INT(num_bindings, &in); - bindings = safe_emalloc(num_bindings, sizeof(sdlBindingPtr), 0); + WSDL_CACHE_GET_INT(num_bindings, in); + if (in->failed || num_bindings < 0 || (size_t)num_bindings > (size_t)st.st_size) { + goto invalid_cache; + } + bindings = safe_emalloc(num_bindings ? num_bindings : 1, sizeof(sdlBindingPtr), 0); + memset(bindings, 0, (num_bindings ? num_bindings : 1) * sizeof(sdlBindingPtr)); if (num_bindings > 0) { sdl->bindings = emalloc(sizeof(HashTable)); zend_hash_init(sdl->bindings, num_bindings, NULL, delete_binding, 0); for (i = 0; i < num_bindings; i++) { sdlBindingPtr binding = emalloc(sizeof(sdlBinding)); memset(binding, 0, sizeof(sdlBinding)); - sdl_deserialize_key(sdl->bindings, binding, &in); - binding->name = sdl_deserialize_string(&in); - binding->location = sdl_deserialize_string(&in); - WSDL_CACHE_GET_1(binding->bindingType,sdlBindingType,&in); - if (binding->bindingType == BINDING_SOAP && *in != 0) { + sdl_deserialize_key(sdl->bindings, binding, in); + binding->name = sdl_deserialize_string(in); + binding->location = sdl_deserialize_string(in); + WSDL_CACHE_GET_1(binding->bindingType,sdlBindingType,in); + if (binding->bindingType == BINDING_SOAP && WSDL_CACHE_PEEK_1(in) != 0) { sdlSoapBindingPtr soap_binding = binding->bindingAttributes = emalloc(sizeof(sdlSoapBinding)); - WSDL_CACHE_GET_1(soap_binding->style,sdlEncodingStyle,&in); - WSDL_CACHE_GET_1(soap_binding->transport,sdlTransport,&in); + memset(soap_binding, 0, sizeof(sdlSoapBinding)); + WSDL_CACHE_GET_1(soap_binding->style,sdlEncodingStyle,in); + WSDL_CACHE_GET_1(soap_binding->transport,sdlTransport,in); } else { - WSDL_CACHE_SKIP(1,&in); + WSDL_CACHE_SKIP(1,in); } bindings[i] = binding; } } /* deserialize functions */ - WSDL_CACHE_GET_INT(num_func, &in); - zend_hash_init(&sdl->functions, num_func, NULL, delete_function, 0); + WSDL_CACHE_GET_INT(num_func, in); + if (in->failed || num_func < 0 || (size_t)num_func > (size_t)st.st_size) { + goto invalid_cache; + } if (num_func > 0) { functions = safe_emalloc(num_func, sizeof(sdlFunctionPtr), 0); + memset(functions, 0, num_func * sizeof(sdlFunctionPtr)); for (i = 0; i < num_func; i++) { int binding_num, num_faults; sdlFunctionPtr func = emalloc(sizeof(sdlFunction)); - sdl_deserialize_key(&sdl->functions, func, &in); - func->functionName = sdl_deserialize_string(&in); - func->requestName = sdl_deserialize_string(&in); - func->responseName = sdl_deserialize_string(&in); - - WSDL_CACHE_GET_INT(binding_num, &in); - if (binding_num == 0) { - func->binding = NULL; - } else { + memset(func, 0, sizeof(sdlFunction)); + sdl_deserialize_key(&sdl->functions, func, in); + func->functionName = sdl_deserialize_string(in); + func->requestName = sdl_deserialize_string(in); + func->responseName = sdl_deserialize_string(in); + + WSDL_CACHE_GET_INT(binding_num, in); + if (binding_num >= 1 && (size_t)binding_num <= (size_t)num_bindings) { func->binding = bindings[binding_num-1]; + } else { + if (binding_num != 0) { + WSDL_CACHE_FAIL(in); + } + func->binding = NULL; } - if (func->binding && func->binding->bindingType == BINDING_SOAP && *in != 0) { + if (func->binding && func->binding->bindingType == BINDING_SOAP && WSDL_CACHE_PEEK_1(in) != 0) { sdlSoapBindingFunctionPtr binding = func->bindingAttributes = emalloc(sizeof(sdlSoapBindingFunction)); memset(binding, 0, sizeof(sdlSoapBindingFunction)); - WSDL_CACHE_GET_1(binding->style,sdlEncodingStyle,&in); - binding->soapAction = sdl_deserialize_string(&in); - sdl_deserialize_soap_body(&binding->input, encoders, types, &in); - sdl_deserialize_soap_body(&binding->output, encoders, types, &in); + WSDL_CACHE_GET_1(binding->style,sdlEncodingStyle,in); + binding->soapAction = sdl_deserialize_string(in); + sdl_deserialize_soap_body(&binding->input, encoders, types, in); + sdl_deserialize_soap_body(&binding->output, encoders, types, in); } else { - WSDL_CACHE_SKIP(1, &in); + WSDL_CACHE_SKIP(1, in); func->bindingAttributes = NULL; } - func->requestParameters = sdl_deserialize_parameters(encoders, types, &in); - func->responseParameters = sdl_deserialize_parameters(encoders, types, &in); + func->requestParameters = sdl_deserialize_parameters(encoders, types, in); + func->responseParameters = sdl_deserialize_parameters(encoders, types, in); - WSDL_CACHE_GET_INT(num_faults, &in); + WSDL_CACHE_GET_INT(num_faults, in); + if (UNEXPECTED((size_t)num_faults > WSDL_CACHE_REMAINING(in))) { + WSDL_CACHE_FAIL(in); + num_faults = 0; + } if (num_faults > 0) { int j; @@ -1878,22 +2065,23 @@ static sdlPtr get_sdl_from_cache(const char *fn, const char *uri, size_t uri_len for (j = 0; j < num_faults; j++) { sdlFaultPtr fault = emalloc(sizeof(sdlFault)); + memset(fault, 0, sizeof(sdlFault)); - sdl_deserialize_key(func->faults, fault, &in); - fault->name =sdl_deserialize_string(&in); - fault->details =sdl_deserialize_parameters(encoders, types, &in); - if (*in != 0) { + sdl_deserialize_key(func->faults, fault, in); + fault->name =sdl_deserialize_string(in); + fault->details =sdl_deserialize_parameters(encoders, types, in); + if (WSDL_CACHE_PEEK_1(in) != 0) { sdlSoapBindingFunctionFaultPtr binding = fault->bindingAttributes = emalloc(sizeof(sdlSoapBindingFunctionFault)); memset(binding, 0, sizeof(sdlSoapBindingFunctionFault)); - WSDL_CACHE_GET_1(binding->use,sdlEncodingUse,&in); + WSDL_CACHE_GET_1(binding->use,sdlEncodingUse,in); if (binding->use == SOAP_ENCODED) { - WSDL_CACHE_GET_1(binding->encodingStyle, sdlRpcEncodingStyle, &in); + WSDL_CACHE_GET_1(binding->encodingStyle, sdlRpcEncodingStyle, in); } else { binding->encodingStyle = SOAP_ENCODING_DEFAULT; } - binding->ns = sdl_deserialize_string(&in); + binding->ns = sdl_deserialize_string(in); } else { - WSDL_CACHE_SKIP(1, &in); + WSDL_CACHE_SKIP(1, in); fault->bindingAttributes = NULL; } } @@ -1905,19 +2093,31 @@ static sdlPtr get_sdl_from_cache(const char *fn, const char *uri, size_t uri_len } /* deserialize requests */ - WSDL_CACHE_GET_INT(i, &in); + WSDL_CACHE_GET_INT(i, in); + if (in->failed || i < 0 || (size_t)i > (size_t)st.st_size) { + goto invalid_cache; + } if (i > 0) { sdl->requests = emalloc(sizeof(HashTable)); zend_hash_init(sdl->requests, i, NULL, NULL, 0); while (i > 0) { int function_num; - WSDL_CACHE_GET_INT(function_num, &in); - sdl_deserialize_key(sdl->requests, functions[function_num-1], &in); + WSDL_CACHE_GET_INT(function_num, in); + if (function_num >= 1 && (size_t)function_num <= (size_t)num_func) { + sdl_deserialize_key(sdl->requests, functions[function_num-1], in); + } else { + WSDL_CACHE_FAIL(in); + sdl_deserialize_key(sdl->requests, NULL, in); + } i--; } } + if (in->failed) { + goto invalid_cache; + } + if (functions) { efree(functions); } @@ -1926,6 +2126,26 @@ static sdlPtr get_sdl_from_cache(const char *fn, const char *uri, size_t uri_len efree(types); efree(buf); return sdl; + +invalid_cache: + unlink(fn); + if (sdl) { + delete_sdl(sdl); + } + if (functions) { + efree(functions); + } + if (bindings) { + efree(bindings); + } + if (encoders) { + efree(encoders); + } + if (types) { + efree(types); + } + efree(buf); + return NULL; } static void sdl_serialize_string(const char *str, smart_str *out) diff --git a/ext/soap/tests/wsdl_cache_corrupted_entries.phpt b/ext/soap/tests/wsdl_cache_corrupted_entries.phpt new file mode 100644 index 000000000000..911ecb23bc07 --- /dev/null +++ b/ext/soap/tests/wsdl_cache_corrupted_entries.phpt @@ -0,0 +1,94 @@ +--TEST-- +soap: reject corrupted WSDL disk cache entries with invalid lengths or out-of-range table indices +--EXTENSIONS-- +soap +--INI-- +soap.wsdl_cache_enabled=1 +soap.wsdl_cache=1 +--SKIPIF-- + +--FILE-- + pack('V', $v); +$u8 = fn(int $v): string => chr($v); +$str = fn(?string $s): string => $s === null ? $u32(0x7FFFFFFF) : $u32(strlen($s)) . $s; + +$header = 'wsdl' . $u8(0x11) . $u8(0) + . pack('q', time() + 86400) /* fresh time_t, default TTL is 86400s */ + . $str($uri); + +/* + * Variant 1: sdl_deserialize_string() trusts a 32-bit length. A 1 MiB length + * is declared with no payload at all, so a reader without end-of-buffer + * validation would read past the emalloc(filesize) file buffer. + */ +$string_oob = $header . $u32(0x00100000); + +/* + * Variant 2: one bindings slot is allocated, then a function references + * binding_num = 2, i.e. an index past the allocated bindings table. + */ +$index_oob = $header + . $str('x') /* sdl->source */ + . $str(null) /* sdl->target_ns */ + . $u32(0) . $u32(0) . $u32(0) . $u32(0) /* groups/types/elements/encoders */ + . $u32(1) /* num_bindings = 1 */ + . $u32(0x7FFFFFFF) /* key: next-index */ + . $str(null) /* binding->name */ + . $str(null) /* binding->location */ + . $u8(2) /* bindingType != BINDING_SOAP */ + . $u8(0) /* soap binding flag, skipped */ + . $u32(1) /* num_func = 1 */ + . $u32(0x7FFFFFFF) /* function key */ + . $str(null) /* functionName */ + . $str(null) /* requestName */ + . $str(null) /* responseName */ + . $u32(2); /* binding_num past the single bindings slot */ + +foreach (['string-oob' => $string_oob, 'index-oob' => $index_oob] as $label => $payload) { + file_put_contents($name, $payload); + + try { + /* A corrupted cache entry must be rejected (and unlinked); the + * constructor then fails fetching the non-existent WSDL. */ + @new SoapClient($uri, ['cache_wsdl' => WSDL_CACHE_DISK]); + } catch (Throwable $e) { + echo $label, " rejected\n"; + } + + var_dump(file_exists($name)); +} +?> +--CLEAN-- + +--EXPECT-- +string-oob rejected +bool(false) +index-oob rejected +bool(false)