<table border="1" cellspacing="0" cellpadding="8">
    <tr>
        <th>Issue</th>
        <td>
            <a href=https://github.com/llvm/llvm-project/issues/78771>78771</a>
        </td>
    </tr>

    <tr>
        <th>Summary</th>
        <td>
            std::copy and others bypass _LIBCPP_ABI_BOUNDED_ITERATORS and other hardened iterators
        </td>
    </tr>

    <tr>
      <th>Labels</th>
      <td>
            new issue
      </td>
    </tr>

    <tr>
      <th>Assignees</th>
      <td>
      </td>
    </tr>

    <tr>
      <th>Reporter</th>
      <td>
          davidben
      </td>
    </tr>
</table>

<pre>
    `std::copy` has a memmove specialization for contiguous iterators. However, this specialization causes hardened iterator types, like `_LIBCPP_ABI_BOUNDED_ITERATORS` to lose their safety checks. This is particularly unfortunate because, as far as I can tell, the simplest way in C++20 to copy between spans is:

```
void copy_span(std::span<const int> src, std::span<int> dst) {
 std::ranges::copy(src, dst.begin());
}
```

This is bounds-safe with respect to `src`, because `std::ranges` takes care of ensuring we act on `src.begin()` and `src.end()` as a pair, but not with respect `dst`. There is no API that takes both input and output as range, as far as I can tell. That means our only option for safety is if `dst.begin()` returning an iterator type that is aware of its bounds. With libc++ bounded iterators, it does so. However, the specialization discards the optimization:

https://godbolt.org/z/Kv13sb7PK

`std::copy_n` has a similar issue because it takes a count, so neither source nor destination pass their bounds.

Looking at libc++ internals, I think the problem is two-fold:

First, `__bounded_iter` does not check `operator+`, only `operator*`, so element-by-element dereference is the only hope we have to bounds-check things. But that's quite incompatible with the `memcpy` optimization. Also, as we see from `copy_span2`, Clang can't figure out to hoist the checks out of the loop. I suspect it doesn't help that `__libcpp_verbose_abort` forces different asserts to emit different messages. Although when I replace it with `__builtin_trap`, it still checks on every loop iteration.

Second, even if libc++ (or an external hardened iterator) checked `operator+`, it doesn't run `operator+` on the output iterator before the `memmove`. We do actually run it *after* the `memmove` in `__rewrap_iter`, but by that point it's too late and we've written out of bounds.
https://github.com/llvm/llvm-project/blob/main/libcxx/include/__algorithm/copy_move_common.h#L104-L108

One thought then is to add more checks to `__bounded_iter` and then, when we're unwrapping to pointers, figure out how many bytes we'll be writing and compute `__out_first + n` first, throwing away the result, just to trigger any hardening checks.
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJyUVk1v4zgS_TX0pRBDpr8PPjjJGBtMY7sx04s5GpRUsjihWFqyaLfn1y-KktNOMrvAAoZtiSyy6tV7jzQx2pNH3Knlo1o-T0zilsKuNmdbl-gnJdXXnVoVkWs136v5vqL-qlYFtCaCgQ67js4IscfKGmf_MmzJQ0MBKvJsT4lSBMsYDFOIU_gHXfCMQekn4NbGj4GVSREjtCbU6LF-iwS-9hglytlXBLUqjl9eHp--fTvuH1-Oj1__9c_nX56PL99_-W3__etvv0uCTOAoInCLNkA0DfIVqhar1ziF77K3jdCbwLZKzgR3heQbCpy8YYQScyqyo4nQmCA_L1AZD4zODfkjRNv1DiPDxVzBenhS-lHpR523F6igRL4geoi98bKloFg8q-L2vSrGT348k61z3FHmK715wz0_z58q8pHBelbzXyCGShL5OGccrSMrvQW1fhzW_jkvGH_CeNdPvRmXqiNPSzxZ2VrprXzmY7haP_9tysP3DdCSkq_jg8ANF8stBJQWs-AhNAqVBOqnG8Bwz60xL2meecUIlQkI1AD6mIL1J7ggmIqB_LjWu1xXBRhf30bQ13fvhau9sZl3ZWLwxO_TU6tC8FoVwg0MKLV4gv23F-DW8JhQSdyC9X3ivBUlzn8j5Mz_G1lkScPQoTCAUgDy7grUv0llJKcg2IyZfKwsIKfgBQPj36tiyM9GMJcRLsu3PkzhDynS2bIaiDm8vxNW1pRlqAkjRPog0E_Crm2sTKhjHpMKunHkA61b5j4TTB-UPpyoLsnxlMJJ6cNfSh9-Pc_msVx_-_WDFt7ZzNH_NJpoO-tMABtjelOnJD40xkBFyXNWA4FHyy0GiJRCheApQI2RrR-K6E2Moy2MMN1n8YXoNePM97hZzxi8cRkv4YT1rxmEPlDpsJMG8IUeGnL1BywONsScmbjWcWzAURog5WXghY7ZmmQO9UNrZOdBK5kv70b240gkQIcden4orw_jX6gxYIMBfZV5nHslS7TUo2ioNWcUQY5iHXaWkk5xCo-JM6WUXkf4d7KMYH1FXW_Ylm5UtSypVkWHXTUcB_dcmMLeRRrVcEGIiNAE6iTizdv0WMGTM_4kUlF6zdDYUxISp2wYLdnIea_Bt_N7avIbR9RP4QViGgQ8cnhYpkXXD7rImEsf-_54xlBSxKMpKYjURXsVRqhtk9ESJUcMHGVv7GTFt5EOYzQnjFIbt5ROLVxa9PACAXtnqszFDM3Q5WQdW3_kYPqxUMsQ2Tr3VosHEdo1VzLqUcC7p87vWJH42JNM9eIOd5RUekNB7AB_DNz8fGyK_-ftsP57Zr1DLST_eZbkmQk0eN2b9ZTYUMA7Isg9IPvnHwg1iU8n49w1r2oZlN6bRjiv95-D5OjMuAW8BNPfxHFz6_I6NLMn6yWFTE0mAidHtTjxBZVenxEuwTKjvxHlnbw_eJLlNpXTijqlD86dbz8PfaA_sWKlD6WjUulDZ8SID4L8jx9KH6yvXKpR6cPxaNyJguVWojO1paBjRV1HftoqPf8yKxYPX2bF5r6tX70AJyTK7PZZpASmrqETUEeGDEfmZ8uQgiVM4MkkzNUHhOQFvV7ci2lACweHv9NVSxfojL9CeWWMQ6xzUA7gDSeM3EG6PvFw0zpS4mMjLgbCu-zKzc3UuA10yVFyBZLOBozJ5bE_U8w65mBPJxSuXkeOSsB4F5vUu3m9nW_NBHezdbFcbRdFsZ20u2aLpamKcjVfLhdlsSoXzXZTV-UWVyu9QDOxO13oRTGbbWfb5Vxvp7ONrmqz3CyberlZrBq1KLAz1k2lsXL6TPLxsVtv1uvZxJkSXcwXX609XoazRWkt9-Cwy2Qo0ymqReFs5PhzFbbscPfurBruA3LqRCiv-YT5nzfUn_M_qzZOUnC7_5uuOf2o9CGX958AAAD__96d8Vo">