X-Git-Url: https://git.stg.codes/stg.git/blobdiff_plain/46b0747592074017ff0ea4b33d4a7194235886e5..HEAD:/libs/smux/per_support.c?ds=inline diff --git a/libs/smux/per_support.c b/libs/smux/per_support.c index c8344193..8f4d4604 100644 --- a/libs/smux/per_support.c +++ b/libs/smux/per_support.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2006 Lev Walkin . All rights reserved. + * Copyright (c) 2005-2017 Lev Walkin . All rights reserved. * Redistribution and modifications are permitted subject to BSD license. */ #include @@ -7,126 +7,64 @@ #include /* - * Extract a small number of bits (<= 31) from the specified PER data pointer. + * X.691-201508 #10.9 General rules for encoding a length determinant. + * Get the optionally constrained length "n" from the stream. */ -int32_t -per_get_few_bits(asn_per_data_t *pd, int nbits) { - size_t off; /* Next after last bit offset */ - uint32_t accum; - const uint8_t *buf; - - if(nbits < 0 || pd->nboff + nbits > pd->nbits) - return -1; +ssize_t +uper_get_length(asn_per_data_t *pd, int ebits, size_t lower_bound, + int *repeat) { + ssize_t value; - ASN_DEBUG("[PER get %d bits from %p+%d bits]", - nbits, pd->buffer, pd->nboff); + *repeat = 0; - /* - * Normalize position indicator. - */ - if(pd->nboff >= 8) { - pd->buffer += (pd->nboff >> 3); - pd->nbits -= (pd->nboff & ~0x07); - pd->nboff &= 0x07; - } - off = (pd->nboff += nbits); - buf = pd->buffer; - - /* - * Extract specified number of bits. - */ - if(off <= 8) - accum = nbits ? (buf[0]) >> (8 - off) : 0; - else if(off <= 16) - accum = ((buf[0] << 8) + buf[1]) >> (16 - off); - else if(off <= 24) - accum = ((buf[0] << 16) + (buf[1] << 8) + buf[2]) >> (24 - off); - else if(off <= 31) - accum = ((buf[0] << 24) + (buf[1] << 16) - + (buf[2] << 8) + (buf[3])) >> (32 - off); - else if(nbits <= 31) { - asn_per_data_t tpd = *pd; - /* Here are we with our 31-bits limit plus 1..7 bits offset. */ - tpd.nboff -= nbits; - accum = per_get_few_bits(&tpd, nbits - 24) << 24; - accum |= per_get_few_bits(&tpd, 24); - } else { - pd->nboff -= nbits; /* Oops, revert back */ - return -1; - } + /* #11.9.4.1 Encoding if constrained (according to effective bits) */ + if(ebits >= 0 && ebits <= 16) { + value = per_get_few_bits(pd, ebits); + if(value >= 0) value += lower_bound; + return value; + } - return (accum & (((uint32_t)1 << nbits) - 1)); -} - -/* - * Extract a large number of bits from the specified PER data pointer. - */ -int -per_get_many_bits(asn_per_data_t *pd, uint8_t *dst, int alright, int nbits) { - int32_t value; - - if(alright && (nbits & 7)) { - /* Perform right alignment of a first few bits */ - value = per_get_few_bits(pd, nbits & 0x07); - if(value < 0) return -1; - *dst++ = value; /* value is already right-aligned */ - nbits &= ~7; - } - - while(nbits) { - if(nbits >= 24) { - value = per_get_few_bits(pd, 24); - if(value < 0) return -1; - *(dst++) = value >> 16; - *(dst++) = value >> 8; - *(dst++) = value; - nbits -= 24; - } else { - value = per_get_few_bits(pd, nbits); - if(value < 0) return -1; - if(nbits & 7) { /* implies left alignment */ - value <<= 8 - (nbits & 7), - nbits += 8 - (nbits & 7); - if(nbits > 24) - *dst++ = value >> 24; - } - if(nbits > 16) - *dst++ = value >> 16; - if(nbits > 8) - *dst++ = value >> 8; - *dst++ = value; - break; - } - } - - return 0; + value = per_get_few_bits(pd, 8); + if((value & 0x80) == 0) { /* #11.9.3.6 */ + return (value & 0x7F); + } else if((value & 0x40) == 0) { /* #11.9.3.7 */ + /* bit 8 ... set to 1 and bit 7 ... set to zero */ + value = ((value & 0x3f) << 8) | per_get_few_bits(pd, 8); + return value; /* potential -1 from per_get_few_bits passes through. */ + } else if(value < 0) { + ASN_DEBUG("END of stream reached for PER"); + return -1; + } + value &= 0x3f; /* this is "m" from X.691, #11.9.3.8 */ + if(value < 1 || value > 4) { + return -1; /* Prohibited by #11.9.3.8 */ + } + *repeat = 1; + return (16384 * value); } /* - * Get the length "n" from the stream. + * Get the normally small length "n". + * This procedure used to decode length of extensions bit-maps + * for SET and SEQUENCE types. */ ssize_t -uper_get_length(asn_per_data_t *pd, int ebits, int *repeat) { - ssize_t value; +uper_get_nslength(asn_per_data_t *pd) { + ssize_t length; - *repeat = 0; + ASN_DEBUG("Getting normally small length"); - if(ebits >= 0) return per_get_few_bits(pd, ebits); - - value = per_get_few_bits(pd, 8); - if(value < 0) return -1; - if((value & 128) == 0) /* #10.9.3.6 */ - return (value & 0x7F); - if((value & 64) == 0) { /* #10.9.3.7 */ - value = ((value & 63) << 8) | per_get_few_bits(pd, 8); - if(value < 0) return -1; - return value; + if(per_get_few_bits(pd, 1) == 0) { + length = per_get_few_bits(pd, 6) + 1; + if(length <= 0) return -1; + ASN_DEBUG("l=%d", (int)length); + return length; + } else { + int repeat; + length = uper_get_length(pd, -1, 0, &repeat); + if(length >= 0 && !repeat) return length; + return -1; /* Error, or do not support >16K extensions */ } - value &= 63; /* this is "m" from X.691, #10.9.3.8 */ - if(value < 1 || value > 4) - return -1; - *repeat = 1; - return (16384 * value); } /* @@ -156,8 +94,8 @@ uper_get_nsnnwn(asn_per_data_t *pd) { } /* - * Put the normally small non-negative whole number. - * X.691, #10.6 + * X.691-11/2008, #11.6 + * Encoding of a normally small non-negative whole number */ int uper_put_nsnnwn(asn_per_outp_t *po, int n) { @@ -182,137 +120,176 @@ uper_put_nsnnwn(asn_per_outp_t *po, int n) { } -/* - * Put a small number of bits (<= 31). - */ -int -per_put_few_bits(asn_per_outp_t *po, uint32_t bits, int obits) { - size_t off; /* Next after last bit offset */ - size_t omsk; /* Existing last byte meaningful bits mask */ - uint8_t *buf; - - if(obits <= 0 || obits >= 32) return obits ? -1 : 0; - - ASN_DEBUG("[PER put %d bits to %p+%d bits]", - obits, po->buffer, po->nboff); - - /* - * Normalize position indicator. - */ - if(po->nboff >= 8) { - po->buffer += (po->nboff >> 3); - po->nbits -= (po->nboff & ~0x07); - po->nboff &= 0x07; - } +/* X.691-2008/11, #11.5.6 -> #11.3 */ +int uper_get_constrained_whole_number(asn_per_data_t *pd, unsigned long *out_value, int nbits) { + unsigned long lhalf; /* Lower half of the number*/ + long half; - /* - * Flush whole-bytes output, if necessary. - */ - if(po->nboff + obits > po->nbits) { - int complete_bytes = (po->buffer - po->tmpspace); - if(po->outper(po->buffer, complete_bytes, po->op_key) < 0) - return -1; - if(po->nboff) - po->tmpspace[0] = po->buffer[0]; - po->buffer = po->tmpspace; - po->nbits = 8 * sizeof(po->tmpspace); - po->flushed_bytes += complete_bytes; + if(nbits <= 31) { + half = per_get_few_bits(pd, nbits); + if(half < 0) return -1; + *out_value = half; + return 0; } - /* - * Now, due to sizeof(tmpspace), we are guaranteed large enough space. - */ - buf = po->buffer; - omsk = ~((1 << (8 - po->nboff)) - 1); - off = (po->nboff += obits); - - /* Clear data of debris before meaningful bits */ - bits &= (((uint32_t)1 << obits) - 1); - - ASN_DEBUG("[PER out %d %u/%x (t=%d,o=%d) %x&%x=%x]", obits, bits, bits, - po->nboff - obits, off, buf[0], omsk&0xff, buf[0] & omsk); - - if(off <= 8) /* Completely within 1 byte */ - bits <<= (8 - off), - buf[0] = (buf[0] & omsk) | bits; - else if(off <= 16) - bits <<= (16 - off), - buf[0] = (buf[0] & omsk) | (bits >> 8), - buf[1] = bits; - else if(off <= 24) - bits <<= (24 - off), - buf[0] = (buf[0] & omsk) | (bits >> 16), - buf[1] = bits >> 8, - buf[2] = bits; - else if(off <= 31) - bits <<= (32 - off), - buf[0] = (buf[0] & omsk) | (bits >> 24), - buf[1] = bits >> 16, - buf[2] = bits >> 8, - buf[3] = bits; - else { - ASN_DEBUG("->[PER out split %d]", obits); - per_put_few_bits(po, bits >> 8, 24); - per_put_few_bits(po, bits, obits - 24); - ASN_DEBUG("<-[PER out split %d]", obits); - } + if((size_t)nbits > 8 * sizeof(*out_value)) + return -1; /* RANGE */ - ASN_DEBUG("[PER out %u/%x => %02x buf+%d]", - bits, bits, buf[0], po->buffer - po->tmpspace); + half = per_get_few_bits(pd, 31); + if(half < 0) return -1; + if(uper_get_constrained_whole_number(pd, &lhalf, nbits - 31)) + return -1; + + *out_value = ((unsigned long)half << (nbits - 31)) | lhalf; return 0; } -/* - * Output a large number of bits. - */ +/* X.691-2008/11, #11.5.6 -> #11.3 */ int -per_put_many_bits(asn_per_outp_t *po, const uint8_t *src, int nbits) { - - while(nbits) { - uint32_t value; - - if(nbits >= 24) { - value = (src[0] << 16) | (src[1] << 8) | src[2]; - src += 3; - nbits -= 24; - if(per_put_few_bits(po, value, 24)) - return -1; - } else { - value = src[0]; - if(nbits > 8) - value = (value << 8) | src[1]; - if(nbits > 16) - value = (value << 8) | src[2]; - if(nbits & 0x07) - value >>= (8 - (nbits & 0x07)); - if(per_put_few_bits(po, value, nbits)) - return -1; - break; - } - } - - return 0; +uper_put_constrained_whole_number_u(asn_per_outp_t *po, unsigned long v, + int nbits) { + if(nbits <= 31) { + return per_put_few_bits(po, v, nbits); + } else { + /* Put higher portion first, followed by lower 31-bit */ + if(uper_put_constrained_whole_number_u(po, v >> 31, nbits - 31)) + return -1; + return per_put_few_bits(po, v, 31); + } } /* + * X.691 (08/2015) #11.9 "General rules for encoding a length determinant" * Put the length "n" (or part of it) into the stream. */ ssize_t -uper_put_length(asn_per_outp_t *po, size_t length) { +uper_put_length(asn_per_outp_t *po, size_t length, int *need_eom) { + int dummy = 0; + if(!need_eom) need_eom = &dummy; + + if(length <= 127) { /* #11.9.3.6 */ + *need_eom = 0; + return per_put_few_bits(po, length, 8) + ? -1 : (ssize_t)length; + } else if(length < 16384) { /* #10.9.3.7 */ + *need_eom = 0; + return per_put_few_bits(po, length|0x8000, 16) + ? -1 : (ssize_t)length; + } + + *need_eom = 0 == (length & 16383); + length >>= 14; + if(length > 4) { + *need_eom = 0; + length = 4; + } + + return per_put_few_bits(po, 0xC0 | length, 8) + ? -1 : (ssize_t)(length << 14); + +} - if(length <= 127) /* #10.9.3.6 */ - return per_put_few_bits(po, length, 8) - ? -1 : (ssize_t)length; - else if(length < 16384) /* #10.9.3.7 */ - return per_put_few_bits(po, length|0x8000, 16) - ? -1 : (ssize_t)length; - length >>= 14; - if(length > 4) length = 4; +/* + * Put the normally small length "n" into the stream. + * This procedure used to encode length of extensions bit-maps + * for SET and SEQUENCE types. + */ +int +uper_put_nslength(asn_per_outp_t *po, size_t length) { + if(length <= 64) { + /* #11.9.3.4 */ + if(length == 0) return -1; + return per_put_few_bits(po, length - 1, 7) ? -1 : 0; + } else { + int need_eom = 0; + if(uper_put_length(po, length, &need_eom) != (ssize_t)length + || need_eom) { + /* This might happen in case of >16K extensions */ + return -1; + } + } + + return 0; +} + +static int +per__long_range(long lb, long ub, unsigned long *range_r) { + unsigned long bounds_range; + if((ub < 0) == (lb < 0)) { + bounds_range = ub - lb; + } else if(lb < 0) { + assert(ub >= 0); + bounds_range = 1 + ((unsigned long)ub + (unsigned long)-(lb + 1)); + } else { + assert(!"Unreachable"); + return -1; + } + *range_r = bounds_range; + return 0; +} - return per_put_few_bits(po, 0xC0 | length, 8) - ? -1 : (ssize_t)(length << 14); +int +per_long_range_rebase(long v, long lb, long ub, unsigned long *output) { + unsigned long range; + + assert(lb <= ub); + + if(v < lb || v > ub || per__long_range(lb, ub, &range) < 0) { + /* Range error. */ + return -1; + } + + /* + * Fundamentally what we're doing is returning (v-lb). + * However, this triggers undefined behavior when the word width + * of signed (v) is the same as the size of unsigned (*output). + * In practice, it triggers the UndefinedSanitizer. Therefore we shall + * compute the ranges accurately to avoid C's undefined behavior. + */ + if((v < 0) == (lb < 0)) { + *output = v-lb; + return 0; + } else if(v < 0) { + unsigned long rebased = 1 + (unsigned long)-(v+1) + (unsigned long)lb; + assert(rebased <= range); /* By construction */ + *output = rebased; + return 0; + } else if(lb < 0) { + unsigned long rebased = 1 + (unsigned long)-(lb+1) + (unsigned long)v; + assert(rebased <= range); /* By construction */ + *output = rebased; + return 0; + } else { + assert(!"Unreachable"); + return -1; + } } +int +per_long_range_unrebase(unsigned long inp, long lb, long ub, long *outp) { + unsigned long range; + + if(per__long_range(lb, ub, &range) != 0) { + return -1; + } + + if(inp > range) { + /* + * We can encode something in the given number of bits that technically + * exceeds the range. This is an avenue for security errors, + * so we don't allow that. + */ + return -1; + } + + if(inp <= LONG_MAX) { + *outp = (long)inp + lb; + } else { + *outp = (lb + LONG_MAX + 1) + (long)((inp - LONG_MAX) - 1); + } + + return 0; +}