/* * %CopyrightBegin% * * Copyright Ericsson AB 2008-2018. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * %CopyrightEnd% */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "sys.h" #include "erl_vm.h" #include "global.h" #include "erl_process.h" #include "error.h" #include "bif.h" #include "erl_binary.h" #include "big.h" #include "zlib.h" typedef void (*ChksumFun)(void *sum_in_out, unsigned char *buf, unsigned buflen); /* Hidden trap target */ static BIF_RETTYPE md5_2(BIF_ALIST_2); static Export chksum_md5_2_exp; void erts_init_bif_chksum(void) { /* Non visual BIF to trap to. */ erts_init_trap_export(&chksum_md5_2_exp, am_erlang, ERTS_MAKE_AM("md5_trap"), 2, &md5_2); } static Eterm do_chksum(ChksumFun sumfun, Process *p, Eterm ioterm, int left, void *sum, int *res, int *err) { Eterm *objp; Eterm obj; int c; DECLARE_ESTACK(stack); unsigned char *bytes = NULL; int numbytes = 0; *err = 0; if (left <= 0 || is_nil(ioterm)) { DESTROY_ESTACK(stack); *res = 0; return ioterm; } if(is_binary(ioterm)) { Uint bitoffs; Uint bitsize; Uint size; Eterm res_term = NIL; unsigned char *bytes; byte *temp_alloc = NULL; ERTS_GET_BINARY_BYTES(ioterm, bytes, bitoffs, bitsize); if (bitsize != 0) { *res = 0; *err = 1; DESTROY_ESTACK(stack); return NIL; } if (bitoffs != 0) { bytes = erts_get_aligned_binary_bytes(ioterm, &temp_alloc); /* The call to erts_get_aligned_binary_bytes cannot fail as we'we already checked bitsize and that this is a binary */ } size = binary_size(ioterm); if (size > left) { Eterm *hp; ErlSubBin *sb; Eterm orig; Uint offset; /* Split the binary in two parts, of which we only process the first */ hp = HAlloc(p, ERL_SUB_BIN_SIZE); sb = (ErlSubBin *) hp; ERTS_GET_REAL_BIN(ioterm, orig, offset, bitoffs, bitsize); sb->thing_word = HEADER_SUB_BIN; sb->size = size - left; sb->offs = offset + left; sb->orig = orig; sb->bitoffs = bitoffs; sb->bitsize = bitsize; sb->is_writable = 0; res_term = make_binary(sb); size = left; } (*sumfun)(sum, bytes, size); *res = size; DESTROY_ESTACK(stack); erts_free_aligned_binary_bytes(temp_alloc); return res_term; } if (!is_list(ioterm)) { *res = 0; *err = 1; DESTROY_ESTACK(stack); return NIL; } /* OK a list, needs to be processed in order, handling each flat list-level as they occur, just like io_list_to_binary would */ *res = 0; ESTACK_PUSH(stack,ioterm); while (!ESTACK_ISEMPTY(stack) && left) { ioterm = ESTACK_POP(stack); if (is_nil(ioterm)) { /* ignore empty lists */ continue; } if(is_list(ioterm)) { L_Again: /* Restart with sublist, old listend was pushed on stack */ objp = list_val(ioterm); obj = CAR(objp); for(;;) { /* loop over one flat list of bytes and binaries until sublist or list end is encountered */ if (is_byte(obj)) { int bsize = 0; for(;;) { if (bsize >= numbytes) { if (!bytes) { bytes = erts_alloc(ERTS_ALC_T_TMP, numbytes = 500); } else { if (numbytes > left) { numbytes += left; } else { numbytes *= 2; } bytes = erts_realloc(ERTS_ALC_T_TMP, bytes, numbytes); } } bytes[bsize++] = (unsigned char) unsigned_val(obj); --left; ioterm = CDR(objp); if (!is_list(ioterm)) { break; } objp = list_val(ioterm); obj = CAR(objp); if (!is_byte(obj)) break; if (!left) { break; } } (*sumfun)(sum, bytes, bsize); *res += bsize; } else if (is_nil(obj)) { ioterm = CDR(objp); if (!is_list(ioterm)) { break; } objp = list_val(ioterm); obj = CAR(objp); } else if (is_list(obj)) { /* push rest of list for later processing, start again with sublist */ ESTACK_PUSH(stack,CDR(objp)); ioterm = obj; goto L_Again; } else if (is_binary(obj)) { int sres, serr; Eterm rest_term; rest_term = do_chksum(sumfun, p, obj, left, sum, &sres, &serr); *res += sres; if (serr != 0) { *err = 1; DESTROY_ESTACK(stack); if (bytes != NULL) erts_free(ERTS_ALC_T_TMP, bytes); return NIL; } left -= sres; if (rest_term != NIL) { Eterm *hp; hp = HAlloc(p, 2); obj = CDR(objp); ioterm = CONS(hp, rest_term, obj); left = 0; break; } ioterm = CDR(objp); if (is_list(ioterm)) { /* objp and obj need to be updated if loop is to continue */ objp = list_val(ioterm); obj = CAR(objp); } } else { *err = 1; DESTROY_ESTACK(stack); if (bytes != NULL) erts_free(ERTS_ALC_T_TMP, bytes); return NIL; } if (!left || is_nil(ioterm) || !is_list(ioterm)) { break; } } /* for(;;) */ } /* is_list(ioterm) */ if (!left) { #ifdef ALLOW_BYTE_TAIL if (is_byte(ioterm)) { /* inproper list with byte tail*/ Eterm *hp; hp = HAlloc(p, 2); ioterm = CONS(hp, ioterm, NIL); } #else ; #endif } else if (!is_list(ioterm) && !is_nil(ioterm)) { /* inproper list end */ #ifdef ALLOW_BYTE_TAIL if (is_byte(ioterm)) { unsigned char b[1]; b[0] = (unsigned char) unsigned_val(ioterm); (*sumfun)(sum, b, 1); ++(*res); --left; ioterm = NIL; } else #endif if is_binary(ioterm) { int sres, serr; ioterm = do_chksum(sumfun, p, ioterm, left, sum, &sres, &serr); *res +=sres; if (serr != 0) { *err = 1; DESTROY_ESTACK(stack); if (bytes != NULL) erts_free(ERTS_ALC_T_TMP, bytes); return NIL; } left -= sres; } else { *err = 1; DESTROY_ESTACK(stack); if (bytes != NULL) erts_free(ERTS_ALC_T_TMP, bytes); return NIL; } } } /* while left and not estack empty */ c = ESTACK_COUNT(stack); if (c > 0) { Eterm *hp = HAlloc(p,2*c); while(!ESTACK_ISEMPTY(stack)) { Eterm st = ESTACK_POP(stack); ioterm = CONS(hp, ioterm, st); hp += 2; } } DESTROY_ESTACK(stack); if (bytes != NULL) erts_free(ERTS_ALC_T_TMP, bytes); return ioterm; } static void adler32_wrap(void *vsum, unsigned char *buf, unsigned buflen) { unsigned long sum = *((unsigned long *) vsum); sum = adler32(sum,buf,buflen); *((unsigned long *) vsum) = sum; } static void crc32_wrap(void *vsum, unsigned char *buf, unsigned buflen) { unsigned long sum = *((unsigned long *) vsum); sum = crc32(sum,buf,buflen); *((unsigned long *) vsum) = sum; } static void md5_wrap(void *vsum, unsigned char *buf, unsigned buflen) { MD5_CTX *ctx = ((MD5_CTX *) vsum); MD5Update(ctx,buf,buflen); } #define BYTES_PER_REDUCTION 10 #define CHUNK_PER_SCHEDULE (BYTES_PER_REDUCTION * CONTEXT_REDS) BIF_RETTYPE crc32_1(BIF_ALIST_1) { unsigned long chksum; int res, err; Eterm rest,res_sum; chksum = crc32(0,NULL,0); rest = do_chksum(&crc32_wrap,BIF_P,BIF_ARG_1,CHUNK_PER_SCHEDULE, (void *) &chksum,&res, &err); BUMP_REDS(BIF_P,res / BYTES_PER_REDUCTION); if (err != 0) { BIF_ERROR(BIF_P, BADARG); } res_sum = erts_make_integer(chksum,BIF_P); if (rest != NIL) { BUMP_ALL_REDS(BIF_P); BIF_TRAP2(bif_export[BIF_crc32_2], BIF_P, res_sum, rest); } BIF_RET(res_sum); } BIF_RETTYPE crc32_2(BIF_ALIST_2) { unsigned long chksum; int res, err; Eterm rest,res_sum; Uint u; if (!term_to_Uint(BIF_ARG_1, &u) || ((u >> 16) >> 16) != 0) { BIF_ERROR(BIF_P, BADARG); } chksum = (unsigned long) u; rest = do_chksum(&crc32_wrap,BIF_P,BIF_ARG_2,CHUNK_PER_SCHEDULE, (void *) &chksum,&res, &err); BUMP_REDS(BIF_P,res / BYTES_PER_REDUCTION); if (err != 0) { BIF_ERROR(BIF_P, BADARG); } res_sum = erts_make_integer(chksum,BIF_P); if (rest != NIL) { BUMP_ALL_REDS(BIF_P); BIF_TRAP2(bif_export[BIF_crc32_2], BIF_P, res_sum, rest); } BIF_RET(res_sum); } BIF_RETTYPE crc32_combine_3(BIF_ALIST_3) { unsigned long chksum1,chksum2; z_off_t length; Uint32 res; Eterm res_sum; Uint u; if (!term_to_Uint(BIF_ARG_1, &u) || ((u >> 16) >> 16) != 0) { BIF_ERROR(BIF_P, BADARG); } chksum1 = (unsigned long) u; if (!term_to_Uint(BIF_ARG_2, &u) || ((u >> 16) >> 16) != 0) { BIF_ERROR(BIF_P, BADARG); } chksum2 = (unsigned long) u; if (!term_to_Uint(BIF_ARG_3, &u) || ((u >> 16) >> 16) != 0) { BIF_ERROR(BIF_P, BADARG); } length = (z_off_t) u; res = (Uint32) crc32_combine(chksum1,chksum2,length); res_sum = erts_make_integer(res,BIF_P); BIF_RET(res_sum); } BIF_RETTYPE adler32_1(BIF_ALIST_1) { unsigned long chksum; int res, err; Eterm rest,res_sum; chksum = adler32(0,NULL,0); rest = do_chksum(&adler32_wrap,BIF_P,BIF_ARG_1,CHUNK_PER_SCHEDULE, (void *) &chksum,&res, &err); BUMP_REDS(BIF_P,res / BYTES_PER_REDUCTION); if (err != 0) { BIF_ERROR(BIF_P, BADARG); } res_sum = erts_make_integer(chksum,BIF_P); if (rest != NIL) { BUMP_ALL_REDS(BIF_P); BIF_TRAP2(bif_export[BIF_adler32_2], BIF_P, res_sum, rest); } BIF_RET(res_sum); } BIF_RETTYPE adler32_2(BIF_ALIST_2) { unsigned long chksum; int res, err; Eterm rest,res_sum; Uint u; if (!term_to_Uint(BIF_ARG_1, &u) || ((u >> 16) >> 16) != 0) { BIF_ERROR(BIF_P, BADARG); } chksum = (unsigned long) u; rest = do_chksum(&adler32_wrap,BIF_P,BIF_ARG_2,CHUNK_PER_SCHEDULE, (void *) &chksum,&res, &err); BUMP_REDS(BIF_P,res / BYTES_PER_REDUCTION); if (err != 0) { BIF_ERROR(BIF_P, BADARG); } res_sum = erts_make_integer(chksum,BIF_P); if (rest != NIL) { BUMP_ALL_REDS(BIF_P); BIF_TRAP2(bif_export[BIF_adler32_2], BIF_P, res_sum, rest); } BIF_RET(res_sum); } BIF_RETTYPE adler32_combine_3(BIF_ALIST_3) { unsigned long chksum1,chksum2; z_off_t length; Uint32 res; Eterm res_sum; Uint u; if (!term_to_Uint(BIF_ARG_1, &u) || ((u >> 16) >> 16) != 0) { BIF_ERROR(BIF_P, BADARG); } chksum1 = (unsigned long) u; if (!term_to_Uint(BIF_ARG_2, &u) || ((u >> 16) >> 16) != 0) { BIF_ERROR(BIF_P, BADARG); } chksum2 = (unsigned long) u; if (!term_to_Uint(BIF_ARG_3, &u) || ((u >> 16) >> 16) != 0) { BIF_ERROR(BIF_P, BADARG); } length = (z_off_t) u; if (length == 0) { /* Workaround for unexpected behaviour in zlib. */ res = (Uint32) chksum1; } else { res = (Uint32) adler32_combine(chksum1,chksum2,length); } res_sum = erts_make_integer(res,BIF_P); BIF_RET(res_sum); } BIF_RETTYPE md5_1(BIF_ALIST_1) { Eterm bin; byte* bytes; Eterm rest; int res, err; MD5_CTX context; MD5Init(&context); rest = do_chksum(&md5_wrap,BIF_P,BIF_ARG_1,100,(void *) &context,&res, &err); if (err != 0) { BUMP_REDS(BIF_P,res); BIF_ERROR(BIF_P, BADARG); } if (rest != NIL) { BUMP_ALL_REDS(BIF_P); bin = new_binary(BIF_P, (byte *) &context, sizeof(MD5_CTX)); BIF_TRAP2(&chksum_md5_2_exp, BIF_P, bin, rest); } BUMP_REDS(BIF_P,res); bin = new_binary(BIF_P, (byte *)NULL, 16); bytes = binary_bytes(bin); MD5Final(bytes, &context); BIF_RET(bin); } /* Hidden trap target */ static BIF_RETTYPE md5_2(BIF_ALIST_2) { byte *bytes; MD5_CTX context; Eterm rest; Eterm bin; int res, err; /* No need to check context, this function cannot be called with unaligned or badly sized context as it's always trapped to. */ bytes = binary_bytes(BIF_ARG_1); sys_memcpy(&context,bytes,sizeof(MD5_CTX)); rest = do_chksum(&md5_wrap,BIF_P,BIF_ARG_2,100,(void *) &context,&res, &err); if (err != 0) { BUMP_REDS(BIF_P,res); BIF_ERROR(BIF_P, BADARG); } if (rest != NIL) { BUMP_ALL_REDS(BIF_P); bin = new_binary(BIF_P, (byte *) &context, sizeof(MD5_CTX)); BIF_TRAP2(&chksum_md5_2_exp, BIF_P, bin, rest); } BUMP_REDS(BIF_P,res); bin = new_binary(BIF_P, (byte *)NULL, 16); bytes = binary_bytes(bin); MD5Final(bytes, &context); BIF_RET(bin); } BIF_RETTYPE md5_init_0(BIF_ALIST_0) { Eterm bin; byte* bytes; bin = erts_new_heap_binary(BIF_P, (byte *)NULL, sizeof(MD5_CTX), &bytes); MD5Init((MD5_CTX *)bytes); BIF_RET(bin); } BIF_RETTYPE md5_update_2(BIF_ALIST_2) { byte *bytes; MD5_CTX context; Eterm rest; Eterm bin; int res, err; byte *temp_alloc = NULL; if ((bytes = erts_get_aligned_binary_bytes(BIF_ARG_1, &temp_alloc)) == NULL) { erts_free_aligned_binary_bytes(temp_alloc); BIF_ERROR(BIF_P, BADARG); } if (binary_size(BIF_ARG_1) != sizeof(MD5_CTX)) { erts_free_aligned_binary_bytes(temp_alloc); BIF_ERROR(BIF_P, BADARG); } sys_memcpy(&context,bytes,sizeof(MD5_CTX)); erts_free_aligned_binary_bytes(temp_alloc); rest = do_chksum(&md5_wrap,BIF_P,BIF_ARG_2,100,(void *) &context,&res, &err); if (err != 0) { BUMP_REDS(BIF_P,res); BIF_ERROR(BIF_P, BADARG); } bin = new_binary(BIF_P, (byte *) &context, sizeof(MD5_CTX)); if (rest != NIL) { BUMP_ALL_REDS(BIF_P); BIF_TRAP2(bif_export[BIF_md5_update_2], BIF_P, bin, rest); } BUMP_REDS(BIF_P,res); BIF_RET(bin); } BIF_RETTYPE md5_final_1(BIF_ALIST_1) { Eterm bin; byte* context; byte* result; MD5_CTX ctx_copy; byte* temp_alloc = NULL; if ((context = erts_get_aligned_binary_bytes(BIF_ARG_1, &temp_alloc)) == NULL) { error: erts_free_aligned_binary_bytes(temp_alloc); BIF_ERROR(BIF_P, BADARG); } if (binary_size(BIF_ARG_1) != sizeof(MD5_CTX)) { goto error; } bin = erts_new_heap_binary(BIF_P, (byte *)NULL, 16, &result); sys_memcpy(&ctx_copy, context, sizeof(MD5_CTX)); erts_free_aligned_binary_bytes(temp_alloc); MD5Final(result, &ctx_copy); BIF_RET(bin); }