Hi David,

I am writing to share my current progress on the strnlen implementation in
kf.cc.

I fully referenced and understood the implementation of the strncpy() from
which I have implemented kf_strnlen by bifurcating the state into two, to
match function definition with unbounded test (where \0 is not present)
also.

1) Truncated read: This checks upto limit n and added constraint for
terminator if exists > n.
2) Full read: I added constraint here if \0 is found within <= n.

Also, added a safety check at the end model->read_bytes to look for buffer
over-flow or poison bytes.

I have a question, strnlen return (size - 1), so I used MINUS_EXPR,  with
lhs_type(), see if the user wrote strnlen(ptr, 5), instead of size_t len =
strnlen(ptr, 5); so would it be valid type to prevent a crash? Is there any
standard way gcc follows for void calls or ignored return values?

Looking for your guidance. I have send you a test suite earlier on mailing
list and also the current kf_strnlen implementation code I have tried is
here:

------------------------



/* Handles strnlen by splitting into two outcomes:
a) Truncated read: The limit 'n' is reached before the null-terminator,
in this case the result is 'n'.
b) Non-truncated read: The null-terminator is found before or at the limit
n,
in this case the result is the number of bytes read before the null
terminator*/

class kf_strnlen : public builtin_known_function
{
public:
  bool matches_call_types_p (const call_details &cd) const final override
  {
    return (cd.num_args () == 2
      && cd.arg_is_pointer_p (0)
      && cd.arg_is_integral_p (1));
  }
  enum built_in_function builtin_code () const final override
  {
    return BUILT_IN_STRNLEN;
  }
  void impl_call_post (const call_details &cd) const final override;
};

void
kf_strnlen::impl_call_post (const call_details &cd) const
{
    class strnlen_call_info : public call_info
    {
    public:
      strnlen_call_info(const call_details &cd,
             const svalue *num_bytes_with_terminator_sval,
            bool truncated_read)
    : call_info (cd),
      m_num_bytes_with_terminator_sval (num_bytes_with_terminator_sval),
      m_truncated_read (truncated_read)
    {
    }

    void print_desc (pretty_printer &pp) const final override
    {
      if (m_truncated_read)
   pp_printf (&pp,
        "when %qE reaches the maximum limit",
        get_fndecl ());
      else
   pp_printf (&pp,
         "when %qE finds the null terminator",
          get_fndecl ());
    }

    bool update_model (region_model *model,
           const exploded_edge *,
           region_model_context *ctxt) const final override
    {
      const call_details cd (get_call_details (model, ctxt));

      const svalue *src_sval = cd.get_arg_svalue (0);  /* Argument 0 is the
 string */
      const region *src_reg
  = model->deref_rvalue (src_sval, cd.get_arg_tree (0), ctxt);
      const svalue *limit_n_sval = cd.get_arg_svalue (1); /* Argument 1 is
the limit n */

      const svalue *result_sval = nullptr; /*Symbolic value for return
value of the call*/

      /*Limit(n) reached before finding null-terminator*/
      if (m_truncated_read)
  {
     result_sval = limit_n_sval;  /* it will return n */

    if (m_num_bytes_with_terminator_sval)  /* is false for poisoned values
*/
      {
        /* the null-terminator is not in bounds of n */
        if (!model->add_constraint (m_num_bytes_with_terminator_sval,
            GT_EXPR,
            limit_n_sval,
            ctxt))
      return false;
      }
    else
      {

      }
  }
      /* found the null-terminator within or on the limit 'n' */
      else
  {

    if (m_num_bytes_with_terminator_sval)
      {
        /* the string length is less than or equal to n */
        if (!model->add_constraint (m_num_bytes_with_terminator_sval,
            LE_EXPR,
            limit_n_sval,
            ctxt))
    return false;

    /* since the strnlen() returns the count of bytes excluding \0 (size -
1)
    it should return count excluding \0 */
        result_sval
  = model->get_manager ()->get_or_create_binop (cd.lhs_type (),
    MINUS_EXPR,
    m_num_bytes_with_terminator_sval,
    model->get_manager ()->get_or_create_int_svalue (cd.lhs_type (), 1));

      }
      else
      {
        return false; /* this bifurcation part is removed once falsed */
      }
  }

  /* this sets the return value */
  if (result_sval)
  {
    cd.maybe_set_lhs (result_sval);
  }
  else
  {

  }

  /* this checks for buffer over read, and poison bytes as a safety check */
  if(result_sval)
  {
  model->read_bytes (src_reg,
  cd.get_arg_tree (0),
  result_sval,
  ctxt);
  }

      return true;
    }


  private:

  /*this symbolic value representing the number of bytes read upto the
first non-terminating character,
  if it does not find one it will be NULL */
    const svalue *m_num_bytes_with_terminator_sval;

  /* if true: we are simulating the 'truncated read'
     if false: we are simulating the 'Full read'*/
    bool m_truncated_read;

    };

    /* Body of kf_strnlen::impl_call_post. */
    if (cd.get_ctxt ())
      {
        /*First, scan for a null terminator as if there were no limit,
        with a null ctxt so no errors reported */
        const region_model *model = cd.get_model ();
        const svalue *ptr_arg_sval = cd.get_arg_svalue (0);
        const region *buf_reg
    = model->deref_rvalue (ptr_arg_sval, cd.get_arg_tree (0), nullptr);
        const svalue *num_bytes_with_terminator_sval
    = model->scan_for_null_terminator (buf_reg,
               cd.get_arg_tree (0),
               nullptr,
               nullptr);
        cd.get_ctxt ()->bifurcate
    (std::make_unique<strnlen_call_info>
        (cd, num_bytes_with_terminator_sval,
         false));
        cd.get_ctxt ()->bifurcate
    (std::make_unique<strnlen_call_info>
        (cd, num_bytes_with_terminator_sval,
         true));
        cd.get_ctxt ()->terminate_path ();
      }
}

Reply via email to