Skip to content

Use memory sanitizer annotations to poison memory not requested by bounds #849

Description

@dsharlet

A common bug is to define a func f with some callback and bounds, where the bounds are incorrect and do not request as much of the input as is actually used.

This can be insidious because unit tests will often test f by making a pipeline that directly connects an input to the f. Because there is no intermediate allocation, the bounds of the func aren't really needed, so the test works. But, when used in a real pipeline with a producer g feeding into f, there are crashes or incorrect results because slinky did not allocate and produce enough of g's output to enable f to run correctly.

An interesting way to make this safer would be to use msan annotations to explicitly poison the inputs to a function f that aren't requested by the bounds.

I think this could be implemented by making a function to wrap the function defined here:

static func make_impl(
callable<T...>&& fn, std::vector<input> inputs, std::vector<output> outputs, call_stmt::attributes attrs = {}) {
callable<T...> impl = std::move(fn);
assert(sizeof...(T) == inputs.size() + outputs.size());
auto wrapper = [impl = std::move(impl)](const call_stmt* op, eval_context& ctx) -> index_t {
return call_impl<T...>(impl, ctx, op, std::make_index_sequence<sizeof...(T)>());
};
return func(std::move(wrapper), std::move(inputs), std::move(outputs), {}, std::move(attrs));
}

Something like:

template <typename... T>
callable<T...> make_msan_wrapper(callable<T...> impl, const std::vector<input>& inputs, const std::vector<output>& outputs) {
   auto wrapper = [=, impl = std::move(impl)] (T... bufs) {
     // `inputs` contains symbolic bounds expressions that indicate what this func requires, as a function of the dimensions in `outputs`.
     // We can use these expressions, along with the actual bounds of the buffers we have, to figure out what the bounds that are valid
     // to access. Once we know this, we can poison the difference of the buffer bounds and the valid bounds. This will cause programs
     // that try to access memory they did not request in the bounds expressions to fail when run with msan.
     poison_invalid_bounds(bufs...);
     impl(bufs...);
     // This could unpoison memory that was poisoned even before we poisoned it above...
     unpoison(bufs...);
   }
}

Potential problems:

  • Race conditions in parallel programs (input buffers could be shared across multiple threads, with different valid regions in each thread).
  • The template code will be tricky :)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions