Esempio n. 1
0
    def _send_request(self, parser, rendered_data):
        """ Send a request and invoke the response parser.

        @param parser: A parser to parse the data
        @type  parser: Function pointer
        @param rendered_data: The request's rendered data to send
        @type  rendered_data: Str

        @return: The response from the server
        @rtype : HttpResponse
        """
        try:
            sock = messaging.HttpSock(self._connection_settings)
        except TransportLayerException as error:
            RAW_LOGGING(f"{error!s}")
            # connection failed
            return HttpResponse()

        success, response = sock.sendRecv(
            rendered_data,
            req_timeout_sec=Settings().max_request_execution_time)
        if not success:
            RAW_LOGGING(response.to_str)
        Monitor().increment_requests_count(self.__class__.__name__)
        return response
Esempio n. 2
0
def apply_checkers(checkers, renderings, global_lock):
    """ Calls each enabled checker from a list of Checker objects

    @param checkers: A list of checkers to apply
    @type  checkers: List[Checker]
    @param renderings: Object containing the rendered sequence information
    @type  renderings: RenderedSequence
    @param global_lock: Lock object used for sync of more than one fuzzing jobs.
    @type  global_lock: thread.Lock

    @return: None
    @rtype : None

    """
    for checker in checkers:
        try:
            if checker.enabled:
                RAW_LOGGING(
                    f"Checker: {checker.__class__.__name__} kicks in\n")
                checker.apply(renderings, global_lock)
                RAW_LOGGING(
                    f"Checker: {checker.__class__.__name__} kicks out\n")
        except Exception as error:
            print(f"Exception {error!s} applying checker {checker}")
            raise
Esempio n. 3
0
    def sendRecv(self, message, req_timeout_sec=600):
        """ Sends a specified request to the server and waits for a response

        @param message: Message to be sent.
        @type message : Str
        @param req_timeout_sec: The time, in seconds, to wait for request to complete
        @type req_timeout_sec : Int

        @return:
            False if failure, True if success
            Response if True returned, Error if False returned
        @rtype : Tuple (Bool, String)

        """
        try:
            self._sendRequest(message)
            if not Settings().use_test_socket:
                response = HttpResponse(self._recvResponse(req_timeout_sec))
            else:
                response = self._sock.recv()
            RAW_LOGGING(f'Received: {response.to_str!r}\n')
            return (True, response)
        except TransportLayerException as error:
            response = HttpResponse(str(error).strip('"\''))
            if 'timed out' in str(error):
                response._status_code = TIMEOUT_CODE
                RAW_LOGGING(
                    f"Reached max req_timeout_sec of {req_timeout_sec}.")
            elif self._contains_connection_closed(str(error)):
                response._status_code = CONNECTION_CLOSED_CODE
                RAW_LOGGING(f"{error!s}")
            return (False, response)
        finally:
            self._closeSocket()
Esempio n. 4
0
    def _refresh(self, last_request):
        """ Refresh server state and response mapping

        @param last_request: Last request to fuzz
        @type  last_request: Request

        @return: The new sequence that was sent during refresh
        @rtype:  Sequence

        """
        # replay the whole sequence except the last request
        new_seq = self._execute_start_of_sequence()

        # re-send the last request and analyze the response w.r.t body schema
        initial_response, response_to_parse = self._render_and_send_data(
            new_seq, last_request)

        if not initial_response:
            return None

        hints = self._map_response_to_current_body_schema(response_to_parse)

        if self._acc_response:
            for tag in hints:
                self._response_values[tag] = hints[tag]

        else:
            self._response_values = hints

        self._refresh_req = False
        RAW_LOGGING("Done refreshing the sequence")
        return new_seq
Esempio n. 5
0
    def _execute_start_of_sequence(self):
        """ Send all requests in the sequence up until the last request

        @return: None
        @rtype : None

        """
        # Copied from InvalidDynamicObjectChecker
        RAW_LOGGING("Re-rendering and sending start of sequence")
        new_seq = sequences.Sequence([])
        for request in self._sequence.requests[:-1]:
            new_seq = new_seq + sequences.Sequence(request)
            initial_response, response_to_parse = self._render_and_send_data(
                new_seq, request)

            # Check to make sure a bug wasn't uncovered while executing the
            # sequence
            if initial_response:
                if initial_response.has_bug_code():
                    self._print_suspect_sequence(new_seq, initial_response)
                    BugBuckets.Instance().update_bug_buckets(
                        new_seq,
                        initial_response.status_code,
                        origin=self.__class__.__name__)

                if self._acc_response:
                    hints = self._map_response_to_current_body_schema(
                        response_to_parse)
                    for tag in hints:
                        self._response_values[tag] = hints[tag]
        return new_seq
    def _render_hijack_request(self, req):
        """ Render the last request of the sequence and inspect the status
        code of the response. If it's any of 20x, we have probably hit a bug.

        @param req: The hijack request.
        @type  req: Request Class object.

        @return: None
        @rtype : None

        """
        self._checker_log.checker_print("Hijack request rendering")
        RAW_LOGGING("Hijack request rendering")
        rendered_data, parser = req.render_current(
            self._req_collection.candidate_values_pool
        )
        rendered_data = self._sequence.resolve_dependencies(rendered_data)
        rendered_data = self._change_user_identity(rendered_data)

        response = self._send_request(parser, rendered_data)
        request_utilities.call_response_parser(parser, response)
        if response and self._rule_violation(self._sequence, response):
            self._print_suspect_sequence(self._sequence, response)
            BugBuckets.Instance().update_bug_buckets(
                self._sequence, response.status_code, origin=self.__class__.__name__, reproduce=False
            )
    def _render_attacker_subsequence(self, req):
        """ Helper to render attacker user and try to hijack @param target_type
        objects.

        @param req: The hijack request.
        @type  req: Request Class object.

        @return: None
        @rtype : None

        """
        # Render subsequnce up to before any producer of @param consumed_types.
        consumed_types = req.consumes
        for stopping_length, req in enumerate(self._sequence):
            # Stop before producing the target type.
            if req.produces.intersection(consumed_types):
                break

        for i in range(stopping_length):
            request = self._sequence.requests[i]
            rendered_data, parser = request.render_current(
                self._req_collection.candidate_values_pool
            )
            rendered_data = self._sequence.resolve_dependencies(rendered_data)
            rendered_data = self._change_user_identity(rendered_data)
            response = self._send_request(parser, rendered_data)
            request_utilities.call_response_parser(parser, response)

        self._checker_log.checker_print("Subsequence rendering up to: {}".\
                            format(stopping_length))
        RAW_LOGGING(f"Subsequence rendering  up to: {stopping_length}")
Esempio n. 8
0
 def decode_buf(buf):
     try:
         return buf.decode(UTF8)
     except Exception as ex:
         if self.ignore_decoding_failures:
             RAW_LOGGING(f'Failed to decode data due to {ex}. \
                 Trying again while ignoring offending bytes.')
             return buf.decode(UTF8, "ignore")
         else:
             raise
def _RAW_LOGGING(log_str):
    """ Wrapper for the raw network logging function.
    Necessary to avoid circular dependency with logging.

    @param log_str: The string to log
    @type  log_str: Str

    @return: None
    @rtype : None

    """
    from utils.logger import raw_network_logging as RAW_LOGGING
    RAW_LOGGING(log_str)
    def _namespace_rule(self):
        """ Try to hijack objects of @param target_types and use them via
        a secondary attacker user.

        @param target_types: The types of the target object to attemp hijack.
        @type  target_types: Set

        @return: None
        @rtype : None

        """
        # For the target types (target dynamic objects), get the latest
        # values which we know will exist due to the previous rendering.
        # We will later on use these old values atop a new rendering.
        hijacked_values = {}
        consumed_types = self._sequence.consumes
        consumed_types = set(itertools.chain(*consumed_types))

        # Exit the checker and do not re-render if nothing is consumed since
        # the checker will have nothing to work on anyways.
        if not consumed_types:
            return

        # Render only last request if not in exhaustive (expensive) mode.
        # If that last request does not consume anything, stop here.
        if self._mode != 'exhaustive' and not self._sequence.last_request.consumes:
            return

        self._render_original_sequence_start(self._sequence)

        for type in consumed_types:
           hijacked_values[type] = dependencies.get_variable(type)

        self._checker_log.checker_print(f"Hijacked values: {hijacked_values}")
        RAW_LOGGING(f"Hijacked values: {hijacked_values}")


        for i, req in enumerate(self._sequence):
            # Render only last request if not in exhaustive (expensive) mode.
            if self._mode != 'exhaustive' and i != self._sequence.length - 1:
                continue
            # Skip requests that are not consumers.
            if not req.consumes:
                continue
            dependencies.reset_tlb()
            self._render_attacker_subsequence(req)

            # Feed hijacked values.
            for type in hijacked_values:
                dependencies.set_variable(type, hijacked_values[type])
            self._render_hijack_request(req)
Esempio n. 11
0
    def resolve_dependencies(self, data):
        """ Renders dependent variables.

        @param data: The rendered payload with dependency placeholders.
        @type data: String

        @return: The rendered payload with dependency placeholders substituted
                    by the respective values parsed from the appropriate
                    responses.
        @rtype : String

        """
        data = str(data).split(dependencies.RDELIM)
        for i in range(1, len(data), 2):
            var_name = data[i]
            data[i] = dependencies.get_variable(var_name)
            if data[i] == 'None':
                RAW_LOGGING(f'Dynamic object {var_name} is set to None!')
        return "".join(data)
Esempio n. 12
0
    def _render_last_request(self, seq):
        """ Render the last request of the sequence and inspect the status
        code of the response. If it's any of 20x, we have probably hit a bug.

        @param seq: The sequence whose last request we will try to render.
        @type  seq: Sequence Class object.

        @return: None
        @rtype : None

        """
        request = seq.last_request
        for rendered_data, parser in\
            request.render_iter(self._req_collection.candidate_values_pool,
                                skip=request._current_combination_id):
            # Hold the lock (because other workers may be rendering the same
            # request) and check whether the current rendering is known from the
            # past to lead to invalid status codes. If so, skip the current
            # rendering.
            if self._lock is not None:
                self._lock.acquire()
            should_skip = Monitor().is_invalid_rendering(request)
            if self._lock is not None:
                self._lock.release()

            # Skip the loop and don't forget to increase the counter.
            if should_skip:
                RAW_LOGGING("Skipping rendering: {}".\
                            format(request._current_combination_id))
                request._current_combination_id += 1
                continue

            rendered_data = seq.resolve_dependencies(rendered_data)

            response = self._send_request(parser, rendered_data)
            request_utilities.call_response_parser(parser, response)
            # Append the rendered data to the sent list as we will not be rendering
            # with the sequence's render function
            seq.append_data_to_sent_list(rendered_data, parser, response)
            if response and self._rule_violation(seq, response):
                self._print_suspect_sequence(seq, response)
                BugBuckets.Instance().update_bug_buckets(
                    seq, response.status_code, origin=self.__class__.__name__)
    def _render_original_sequence(self, seq):
        """ Helper to re-render original sequence to have better checkers'
        independence.

        @param seq: The sequence whose last request we will try to render.
        @type  seq: Sequence Class object.

        @return: None
        @rtype : None

        """
        self._checker_log.checker_print("\nRe-rendering original sequence")
        RAW_LOGGING("Re-rendering original sequence")

        for request in seq:
            rendered_data, parser = request.render_current(
                self._req_collection.candidate_values_pool)
            rendered_data = seq.resolve_dependencies(rendered_data)
            response = self._send_request(parser, rendered_data)
            request_utilities.call_response_parser(parser, response)
    def _render_original_sequence_start(self, seq):
        """ Helper to re-render the start of the original sequence to create
        the appropriate dynamic objects. Does not send the final target request.

        @param seq: The sequence whose last request we will try to render.
        @type  seq: Sequence Class object.

        @return: None
        @rtype : None

        """
        self._checker_log.checker_print("\nRe-rendering start of original sequence")
        RAW_LOGGING("Re-rendering start of original sequence")

        for request in seq.requests[:-1]:
            rendered_data, parser = request.render_current(
                self._req_collection.candidate_values_pool
            )
            rendered_data = seq.resolve_dependencies(rendered_data)
            response = self._send_request(parser, rendered_data)
            request_utilities.call_response_parser(parser, response)
Esempio n. 15
0
    def _sendRequest(self, message):
        """ Sends message via current instance of socket object.

        @param message: Message to be sent.
        @type message : Str

        @return: None
        @rtype : None

        """
        def _get_end_of_header(message):
            return message.index(DELIM)

        def _get_start_of_body(message):
            return _get_end_of_header(message) + len(DELIM)

        def _append_to_header(message, content):
            header = message[:_get_end_of_header(message
                                                 )] + "\r\n" + content + DELIM
            return header + message[_get_start_of_body(message):]

        if "Content-Length: " not in message:
            contentlen = len(message[_get_start_of_body(message):])
            message = _append_to_header(message,
                                        f"Content-Length: {contentlen}")
        if self.connection_settings.include_user_agent:
            message = _append_to_header(
                message, f"User-Agent: restler/{Settings().version}")

        # Attempt to throttle the request if necessary
        self._begin_throttle_request()

        try:
            RAW_LOGGING(f'Sending: {message!r}\n')
            self._sock.sendall(message.encode(UTF8))
        except Exception as error:
            raise TransportLayerException(f"Exception Sending Data: {error!s}")
        finally:
            self._end_throttle_request()
Esempio n. 16
0
def try_async_poll(request_data, response, max_async_wait_time):
    """ Helper that will poll the server until a certain resource
    becomes available.

    This function will check the response from a PUT or POST request to
    see if it contains one of the known async resource creations strings and,
    if so, will poll the URL specified in the response until the resource is
    said to be available or a max timeout has been reached.

    @param request_data: The request that was sent's data string
    @type  request_data: Str
    @param response: The response returned after @request was sent
    @type  response: Str
    @param max_async_wait_time: The maximum amount of time we will wait (in
                                seconds) for the resource to become available
    @type  max_async_wait_time: Int

    @return: A tuple containing:
             - The response for parsing, which will either be the @response
             passed to this function or the response from the GET request
             if it was an async request.
             - A boolean that is True if there was an error when creating the
             resource
             - A Boolean that tells the caller whether or not async polling was
             attempted
    @rtype : Tuple(HttpResponse, boolean, boolean)

    """
    from utils.logger import raw_network_logging as RAW_LOGGING
    from utils.logger import print_async_results as LOG_RESULTS

    response_to_parse = response
    resource_error = False
    async_waited = False
    if  Settings().wait_for_async_resource_creation and max_async_wait_time > 0\
    and (request_data.startswith("PUT") or request_data.startswith("POST")):
        # Get the request used for polling the resource availability
        data, data_in_poll_response = get_polling_request(response)
        if data:
            async_waited = True
            poll_wait_seconds = 1
            start_time = time.time()
            RAW_LOGGING("Waiting for resource to be available...")
            while (time.time() - start_time) < max_async_wait_time:
                try:
                    # Send the polling request
                    poll_response = request_utilities.send_request_data(data)
                    time_str = str(round((time.time() - start_time), 2))
                    if data_in_poll_response:
                        # If this returned a '200' status code, the response should contain the parsable data.
                        # Otherwise, continue to poll as the resource has not yet been created. These types will
                        # return a '202 - Accepted' while the resource is still being created.
                        if poll_response.status_code == '200':
                            LOG_RESULTS(
                                request_data,
                                f"Resource creation succeeded after {time_str} seconds."
                            )
                            # Break and return the polling response to be parsed
                            response_to_parse = poll_response
                            break
                    else:
                        # The data is not in the polling response and must be fetched separately.
                        # This comes from Azure-Async responses that do not contain a Location: field
                        # Check for the status of the resource
                        response_body = json.loads(poll_response.json_body)
                        done = str(response_body["status"]).lower()
                        if done == "succeeded":
                            LOG_RESULTS(
                                request_data,
                                f"Resource creation succeeded after {time_str} seconds."
                            )
                            get_response = try_parse_GET_request(request_data)
                            if get_response:
                                response_to_parse = get_response
                            break
                        elif done == "failed":
                            LOG_RESULTS(
                                request_data,
                                f"The server reported that the resource creation Failed after {time_str} seconds."
                            )
                            resource_error = True
                            break
                except json.JSONDecodeError as err:
                    LOG_RESULTS(
                        request_data,
                        "Failed to parse body of async response, retrying.")
                    # This may have been due to a connection failure. Retry until max_async_wait_time.
                    time.sleep(poll_wait_seconds)
                    continue
                except Exception as err:
                    LOG_RESULTS(
                        request_data,
                        f"An exception occurred while parsing the poll message: {err!s}"
                    )
                    break

                try:
                    if not poll_response.status_code.startswith('2'):
                        LOG_RESULTS(
                            request_data,
                            f"Resource creation failed after {time_str} seconds"
                            f" because status code '{poll_response.status_code}' was received."
                        )
                        break
                except Exception as err:
                    LOG_RESULTS(
                        request_data,
                        f"An exception occurred while parsing the poll message: {err!s}"
                    )
                    break
                time.sleep(poll_wait_seconds)
            else:
                RAW_LOGGING(
                    "Resource polling timed out before the resource was available."
                )
                LOG_RESULTS(
                    request_data,
                    f"Failed to create resource in {max_async_wait_time} seconds."
                )
                RAW_LOGGING("Attempting to get resources from GET request...")
                get_response = try_parse_GET_request(request_data)
                if get_response:
                    # Use response from the GET request for parsing. If the GET request failed,
                    # the caller will know to try and use the original PUT response for parsing
                    response_to_parse = get_response

    return response_to_parse, resource_error, async_waited
Esempio n. 17
0
def get_polling_request(response):
    """ Helper that exctracts the URL from an azure-async-operation response
    and then creates and returns a valid GET request for it.

    @param response: The response to parse for the URL
    @type  response: Str

    @return: Tuple containing the rendered data for the GET request
             and a boolean that indicates whether or not to expect
             the parsable data to be included in the polling request.
    @rtype : Tuple(str, bool)

    """
    from utils.logger import raw_network_logging as RAW_LOGGING

    data_in_poll_response = False
    if not response or not response.to_str:
        return None, False
    # In our experience with Azure cloud services, there have been two types of indications for async resource creation.
    # One that contains "Azure-AsyncOperation:<polling-url>" in the response and one that contains "Location:<polling-url>"
    # in the response.
    # - For responses that contain only the Azure-AsyncOperation type, the response from the GET request sent
    #   to the supplied polling-URL returns only a "status" that says InProgress, Succeeded, or Failed. Any additional,
    #   parsable, data about the resource must be retrieved by creating and sending a GET request to the original URI.
    # - For responses that contain the Location: type, the GET request sent to the provided polling-url will include the
    #   parsable resource data along with its "provisioningState", which can be Creating, Succeeded, or Failed.
    #   The Location: types will also be returned with "202 - Accepted" status codes. These responses will look very much
    #   like the original response from the PUT request, so they can be parsed as they would normally.
    # - It is possible to see both Azure-Async AND Location in the response. If this is the case (at least in our experience),
    #   the response will act as though it is the Location type and the response will contain the parsable data.
    # See https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md#x-ms-long-running-operation-options
    # for more information on async resource creation in Azure.
    LocationSearchStr = "Location: "
    AzureAsyncSearchStr = "Azure-AsyncOperation: "
    response_str = response.to_str
    if response.status_code == '202' or response.status_code == '201' and LocationSearchStr in response_str:
        url = response_str.split(LocationSearchStr)[1]
        data_in_poll_response = True
    elif AzureAsyncSearchStr in response_str:
        url = response_str.split(AzureAsyncSearchStr)[1]
    else:
        return None, False

    url_split = url.split("https://")
    if len(url_split) > 1:
        url = url_split[1]
    else:
        return None, False
    try:
        url_part = url.partition('/')
        hostname = url_part[0]
        url = url_part[1] + url_part[2]
        token_value = request_utilities.get_latest_token_value()
        if token_value is None:
            token_value = ''
        # Create the GET request to poll the url
        req_data = 'GET ' + url.split('\r\n')[0] +\
            " HTTP/1.1\r\nAccept: application/json\r\nHost: " +\
            hostname + "\r\nContent-Type: application/json\r\n" +\
            token_value + "\r\n"
        return req_data, data_in_poll_response
    except Exception as err:
        # Log any exception and then return that no polling request exists.
        # It is better to skip polling and continue the fuzzing run than to crash or end.
        # The side effect will be that RESTler may attempt to use a resource before it is
        # created, which may lower coverage during a smoke test, but could also potentially
        # lead to a bug being uncovered in the service being fuzzed.
        RAW_LOGGING(f"Exception while creating the polling request: {err!s}")
        return None, False
Esempio n. 18
0
    def apply_destructors(self, destructors, max_aged_objects=100):
        """ Background task trying to delete evicted objects that are in
            @overflowing dictionary.

        @param overflowing: Dictionary of overflowing objects. We need to issue
                                deletes to all these objects
        @type  overflowing: Dict
        @param destructors: The Request class objects required to delete.
        @type  destructors: Dict
        @param max_aged_objects: Maximum number of objects we allow to age
                                    and will retry to delete (since delete
                                    is idempotent this is fine).
        @type  max_aged_objects: Int

        @return: None
        @rtype : None

        NOTE: This function is invoked without any lock since overflowing
        objects are already dead (not referenced by anything) and are just
        aging here.

        """
        if not self.overflowing:
            return

        from engine.errors import TransportLayerException
        from engine.transport_layer import messaging
        from utils.logger import raw_network_logging as RAW_LOGGING
        from utils.logger import garbage_collector_logging as CUSTOM_LOGGING
        # For each object in the overflowing area, whose destructor is
        # available, render the corresponding request, send the request,
        # and then check the status code. If the resource has been determined
        # to be removed, delete the object from the overflow area.
        # At the end keep track of only up to @param max_aged_objects
        # remaining objects.
        for type in destructors:
            destructor = destructors[type]
            deleted_list = []

            if self.overflowing[type]:
                CUSTOM_LOGGING("{}: Trying garbage collection of * {} * objects".\
                format(formatting.timestamp(), len(self.overflowing[type])))
                CUSTOM_LOGGING(f"{type}: {self.overflowing[type]}")

            # Iterate in reverse to give priority to newest resources
            for value in reversed(self.overflowing[type]):
                rendered_data, _ = destructor.\
                    render_current(self.req_collection.candidate_values_pool)

                # replace dynamic parameters
                fully_rendered_data = str(rendered_data)
                fully_rendered_data = fully_rendered_data.replace(
                    RDELIM + type + RDELIM, value)

                if fully_rendered_data:
                    try:
                        # Establish connection to the server
                        sock = messaging.HttpSock(
                            Settings().connection_settings)
                    except TransportLayerException as error:
                        RAW_LOGGING(f"{error!s}")
                        return

                    # Send the request and receive the response
                    success, response = sock.sendRecv(fully_rendered_data)
                    if success:
                        self.monitor.increment_requests_count('gc')
                    else:
                        RAW_LOGGING(response.to_str)

                    # Check to see if the DELETE operation is complete
                    try:
                        if response.status_code in DELETED_CODES:
                            deleted_list.append(value)
                    except Exception:
                        pass

            # Remove deleted items from the to-delete cache
            for value in deleted_list:
                self.overflowing[type].remove(value)
            self.overflowing[type] = self.overflowing[type][-max_aged_objects:]
Esempio n. 19
0
    def render(self,
               candidate_values_pool,
               lock,
               preprocessing=False,
               postprocessing=False):
        """ Core routine that performs the rendering of restler sequences. In
        principal all requests of a sequence are being constantly rendered with
        a specific values combination @param request._current_combination_id
        which we know in the past led to a valid rendering and only the last
        request of the sequence is being rendered iteratively with all feasible
        value combinations. Each time a "valid rendering" is found for the last
        request of the sequence (where "valid rendering" is defined according
        to "VALID_CODES"), the routine returns a new sequence which has an
        end-to-end (i.e., all requests) "valid rendering" and can be added in
        the sequences collection in order to be used in the future as a building
        block for longer sequences.


        @param candidate_values_pool: The pool of values for primitive types.
        @type candidate_values_pool: Dict
        @param lock: Lock object used for sync of more than one fuzzing jobs.
        @type  lock: thread.Lock object
        @param preprocessing: Set to true if rendering during preprocessing
        @type  preprocessing: Bool

        @return: A RenderedSequence object containing the sequence, the final
                 request's response, whether or not the final request received
                 a valid status code, and a FailureInformation enum if there was
                 a failure or bug detected during rendering.
        @rtype : RenderedSequence
        """
        # Try rendering  all primitive type value combinations for last request
        request = self.last_request

        # for clarity reasons, don't log requests whose render iterator is over
        if request._current_combination_id <\
                request.num_combinations(candidate_values_pool):
            CUSTOM_LOGGING(self, candidate_values_pool)

        self._sent_request_data_list = []
        for rendered_data, parser in\
                request.render_iter(candidate_values_pool,
                                    skip=request._current_combination_id,
                                    preprocessing=preprocessing):
            # Hold the lock (because other workers may be rendering the same
            # request) and check whether the current rendering is known from the
            # past to lead to invalid status codes. If so, skip the current
            # rendering.
            if lock is not None:
                lock.acquire()
            should_skip = Monitor().is_invalid_rendering(request)
            if lock is not None:
                lock.release()

            # Skip the loop and don't forget to increase the counter.
            if should_skip:
                RAW_LOGGING("Skipping rendering: {}".\
                            format(request._current_combination_id))
                request._current_combination_id += 1
                continue

            # Clean up internal state
            self.status_codes = []
            dependencies.reset_tlb()

            sequence_failed = False
            # Step A: Static template rendering
            # Render last known valid combination of primitive type values
            # for every request until the last
            for i in range(len(self.requests) - 1):
                prev_request = self.requests[i]
                prev_rendered_data, prev_parser =\
                    prev_request.render_current(candidate_values_pool,
                    preprocessing=preprocessing)

                # substitute reference placeholders with resolved values
                if not Settings().ignore_dependencies:
                    prev_rendered_data =\
                        self.resolve_dependencies(prev_rendered_data)

                prev_req_async_wait = Settings(
                ).get_max_async_resource_creation_time(prev_request.request_id)
                prev_producer_timing_delay = Settings(
                ).get_producer_timing_delay(prev_request.request_id)

                prev_response = request_utilities.send_request_data(
                    prev_rendered_data)
                prev_response_to_parse, resource_error, async_waited = async_request_utilities.try_async_poll(
                    prev_rendered_data, prev_response, prev_req_async_wait)
                prev_parser_threw_exception = False
                # Response may not exist if there was an error sending the request or a timeout
                if prev_parser and prev_response_to_parse:
                    prev_parser_threw_exception = not request_utilities.call_response_parser(
                        prev_parser, prev_response_to_parse, prev_request)
                prev_status_code = prev_response.status_code

                # If the async logic waited for the resource, this wait already included the required
                # producer timing delay. Here, set the producer timing delay to zero, so this wait is
                # skipped both below for this request and during replay
                if async_waited:
                    prev_producer_timing_delay = 0
                else:
                    prev_req_async_wait = 0

                self.append_data_to_sent_list(prev_rendered_data, prev_parser,
                                              prev_response,
                                              prev_producer_timing_delay,
                                              prev_req_async_wait)

                if not prev_status_code:
                    logger.write_to_main(
                        f"Error: Failed to get status code during valid sequence re-rendering.\n"
                    )
                    sequence_failed = True
                    break

                if prev_response.has_bug_code():
                    BugBuckets.Instance().update_bug_buckets(self,
                                                             prev_status_code,
                                                             reproduce=False,
                                                             lock=lock)
                    sequence_failed = True
                    break

                if prev_parser_threw_exception:
                    logger.write_to_main(
                        "Error: Parser exception occurred during valid sequence re-rendering.\n"
                    )
                    sequence_failed = True
                    break

                if resource_error:
                    logger.write_to_main(
                        "Error: The resource was left in a Failed state after creation during valid sequence re-rendering.\n"
                    )
                    sequence_failed = True
                    break

                # If the previous request is a resource generator and we did not perform an async resource
                # creation wait, then wait for the specified duration in order for the backend to have a
                # chance to create the resource.
                if prev_producer_timing_delay > 0 and prev_request.is_resource_generator(
                ):
                    print(
                        f"Pausing for {prev_producer_timing_delay} seconds, request is a generator..."
                    )
                    time.sleep(prev_producer_timing_delay)

                # register latest client/server interaction
                timestamp_micro = int(time.time() * 10**6)
                self.status_codes.append(
                    status_codes_monitor.RequestExecutionStatus(
                        timestamp_micro,
                        prev_request.hex_definition, prev_status_code,
                        prev_response.has_valid_code(), False))

            if sequence_failed:
                self.status_codes.append(
                    status_codes_monitor.RequestExecutionStatus(
                        int(time.time() * 10**6), request.hex_definition,
                        RESTLER_INVALID_CODE, False, True))
                Monitor().update_status_codes_monitor(self, self.status_codes,
                                                      lock)
                return RenderedSequence(
                    failure_info=FailureInformation.SEQUENCE)

            # Step B: Dynamic template rendering
            # substitute reference placeholders with ressoved values
            # for the last request
            if not Settings().ignore_dependencies:
                rendered_data = self.resolve_dependencies(rendered_data)

            # Render candidate value combinations seeking for valid error codes
            request._current_combination_id += 1

            req_async_wait = Settings().get_max_async_resource_creation_time(
                request.request_id)

            response = request_utilities.send_request_data(rendered_data)
            response_to_parse, resource_error, _ = async_request_utilities.try_async_poll(
                rendered_data, response, req_async_wait)
            parser_exception_occurred = False
            # Response may not exist if there was an error sending the request or a timeout
            if parser and response_to_parse:
                parser_exception_occurred = not request_utilities.call_response_parser(
                    parser, response_to_parse, request)
            status_code = response.status_code
            if not status_code:
                return RenderedSequence(None)

            self.append_data_to_sent_list(rendered_data,
                                          parser,
                                          response,
                                          max_async_wait_time=req_async_wait)

            rendering_is_valid = not parser_exception_occurred\
                and not resource_error\
                and response.has_valid_code()
            # register latest client/server interaction and add to the status codes list
            response_datetime = datetime.datetime.now(datetime.timezone.utc)
            timestamp_micro = int(response_datetime.timestamp() * 10**6)

            self.status_codes.append(
                status_codes_monitor.RequestExecutionStatus(
                    timestamp_micro, request.hex_definition, status_code,
                    rendering_is_valid, False))

            # add sequence's error codes to bug buckets.
            if response.has_bug_code():
                BugBuckets.Instance().update_bug_buckets(self,
                                                         status_code,
                                                         lock=lock)

            Monitor().update_status_codes_monitor(self, self.status_codes,
                                                  lock)

            # Register current rendering's status.
            if lock is not None:
                lock.acquire()
            Monitor().update_renderings_monitor(request, rendering_is_valid)
            if lock is not None:
                lock.release()

            if Monitor().remaining_time_budget <= 0 and not postprocessing:
                raise TimeOutException("Exceeded Timeout")

            if lock is not None:
                lock.acquire()
            # Deep  copying here will try copying anything the class has access
            # to including the shared client monitor, which we update in the
            # above code block holding the lock, but then we release the
            # lock and one thread can be updating while another is copying.
            # This is a typlical nasty read after write syncronization bug.
            duplicate = copy.deepcopy(self)
            if lock is not None:
                lock.release()

            datetime_format = "%Y-%m-%d %H:%M:%S"
            # return a rendered clone if response indicates a valid status code
            if rendering_is_valid or Settings().ignore_feedback:
                return RenderedSequence(
                    duplicate,
                    valid=True,
                    final_request_response=response,
                    response_datetime=response_datetime.strftime(
                        datetime_format))
            else:
                information = None
                if response.has_valid_code():
                    if parser_exception_occurred:
                        information = FailureInformation.PARSER
                    elif resource_error:
                        information = FailureInformation.RESOURCE_CREATION
                elif response.has_bug_code():
                    information = FailureInformation.BUG
                return RenderedSequence(
                    duplicate,
                    valid=False,
                    failure_info=information,
                    final_request_response=response,
                    response_datetime=response_datetime.strftime(
                        datetime_format))

        return RenderedSequence(None)