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 ();
}
}