Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiarch tests #12

Merged
merged 19 commits into from
Dec 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/test-multiarch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Test multi-arch
run-name: ${{ github.actor }} is running multi-arch tests
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
Tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
alpine-arch: [ 'x86_64', 'x86', 'aarch64', 'armhf', 'armv7', 'ppc64le', 'riscv64', 's390x' ]
name: alpine latest-stable ${{ matrix.alpine-arch }} / Python3.x
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: jirutka/setup-alpine@v1
with:
arch: ${{ matrix.alpine-arch }}
version: latest-stable
packages: build-base python3 py3-pip python3-dev
- name: Install cbrrr python module
run: |
python3 -m venv .venv
.venv/bin/python3 -m pip install -v .[fixtures]
shell: alpine.sh {0}
- name: Run the tests
run: |
.venv/bin/python3 -m unittest -v
shell: alpine.sh {0}
15 changes: 11 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
name: Run tests
run-name: ${{ github.actor }} is running tests
on: push
name: Test multi-python-version
run-name: ${{ github.actor }} is running multi-python-version tests
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
Tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13' ]
name: Python ${{ matrix.python-version }} tests
name: ubuntu-latest Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
with:
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,11 @@ encoded = cbrrr.encode_dag_cbor(
decoded = cbrrr.decode_dag_cbor(encoded, cid_ctor=multiformats.CID.decode)
print(decoded) # zb2rhZfjRh2FHHB2RkHVEvL2vJnCTcu7kwRqgVsf9gpkLgteo
```

## Running Tests

```sh
# clone the repo
python3 -m pip install -ve .
python3 -m unittest -v
```
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
Extension(
"cbrrr._cbrrr",
sources=["src/cbrrr/_cbrrr.c"],
extra_compile_args=["-O3", "-Wall", "-Wextra", "-Wpedantic", "-std=c99"],
extra_compile_args=["-O3", "-Wall", "-Wextra", "-Wpedantic", "-std=c99", "-Werror"], # sorry, I hate Werror too, but this code is security-sensive and it's much better to have no build than to have an insecure build. please file a github issue if you're hitting this.
),
],
)
69 changes: 51 additions & 18 deletions src/cbrrr/_cbrrr.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@

#include <string.h>
#include <stdint.h>
#include <limits.h>

#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]

/* If you're compiling on a 32-bit platform, commenting this out should "work",
but I make no guarantees about the safety of the resulting code. I think
there are some lurking integer-overflow-adjacent bugs that could be
triggered by malicious inputs. (frankly, the same caveat applies to 64-bit
too, but I've at least fuzz-tested it a bit)

It should be safe enough if you're self-hosting a PDS, but think twice about
e.g. parsing the atproto firehose.
*/
STATIC_ASSERT(sizeof(size_t) == 8, _64bit_platforms_only); // this'll hopefully be relaxed in the future

// XXX: not sure having these as globals is the right thing to do?
Expand Down Expand Up @@ -184,6 +194,8 @@ cbrrr_bytes_to_b32_multibase(const uint8_t *data, size_t data_len)
}


// return value is length of input that was parsed, or -1 on error.
// result is stored in `value`.
static size_t
cbrrr_parse_minimal_varint(const uint8_t *buf, size_t len, uint64_t *value)
{
Expand Down Expand Up @@ -251,29 +263,38 @@ static size_t
cbrrr_parse_raw_string(const uint8_t *buf, size_t len, DCMajorType type, const uint8_t **str, size_t *str_len)
{
size_t idx = 0, res;
uint64_t actual_str_len;

if (len < idx + 1) {
PyErr_SetString(PY_CBRRR_DECODE_ERROR, "not enough bytes left in buffer");
return -1;
}
*str_len = buf[idx++];
if ((*str_len >> 5) != type) {
PyErr_Format(PY_CBRRR_DECODE_ERROR, "unexpected type (%lu), expected %lu", (*str_len >> 5), type);
actual_str_len = buf[idx++];
if ((actual_str_len >> 5) != type) {
PyErr_Format(PY_CBRRR_DECODE_ERROR, "unexpected type (%lu), expected %lu", (actual_str_len >> 5), type);
return -1;
}
*str_len &= 0x1f;
res = cbrrr_parse_minimal_varint(&buf[idx], len-idx, (uint64_t*)str_len);
actual_str_len &= 0x1f;
res = cbrrr_parse_minimal_varint(&buf[idx], len-idx, &actual_str_len);
if (res == (size_t)-1) {
// python error has been set by cbrrr_parse_minimal_varint
return -1;
}

// should only be plausible on 32-bit platforms
if (idx > SIZE_MAX - res) {
PyErr_SetString(PY_CBRRR_DECODE_ERROR, "index overflow");
return -1;
}
idx += res;
if (*str_len > len - idx) {

if (actual_str_len > (uint64_t)len - idx) { // should also handle cases where actual_str_len is > SIZE_MAX
PyErr_SetString(PY_CBRRR_DECODE_ERROR, "not enough bytes left in buffer");
return -1;
}
*str = &buf[idx];
return idx + *str_len;
*str_len = actual_str_len;
return idx + actual_str_len;
}

// returns number of bytes parsed, -1 on failure
Expand Down Expand Up @@ -340,6 +361,12 @@ cbrrr_parse_token(const uint8_t *buf, size_t len, DCToken *token, PyObject *cid_
// python error set by cbrrr_parse_minimal_varint
return -1;
}

// should only be plausible on 32-bit platforms
if (idx > SIZE_MAX - res) {
PyErr_SetString(PY_CBRRR_DECODE_ERROR, "index overflow");
return -1;
}
idx += res;

// at this point, `info` represents its actual value, with meaning depending on the major type
Expand Down Expand Up @@ -391,7 +418,7 @@ cbrrr_parse_token(const uint8_t *buf, size_t len, DCToken *token, PyObject *cid_
}
return idx + info;
case DCMT_TEXT_STRING:
if (info > len - idx) {
if (info > (uint64_t)len - idx) {
PyErr_SetString(PY_CBRRR_DECODE_ERROR, "not enough bytes left in buffer");
return -1;
}
Expand All @@ -401,7 +428,7 @@ cbrrr_parse_token(const uint8_t *buf, size_t len, DCToken *token, PyObject *cid_
}
return idx + info;
case DCMT_ARRAY:
if (info > len - idx) {
if (info > (uint64_t)len - idx) {
PyErr_SetString(PY_CBRRR_DECODE_ERROR, "not enough bytes left in buffer for an array that long");
return -1;
}
Expand All @@ -412,7 +439,7 @@ cbrrr_parse_token(const uint8_t *buf, size_t len, DCToken *token, PyObject *cid_
token->count = info;
return idx;
case DCMT_MAP:
if (info > len - idx) {
if (info > (uint64_t)len - idx) {
PyErr_SetString(PY_CBRRR_DECODE_ERROR, "not enough bytes left in buffer for a map that long");
return -1;
}
Expand Down Expand Up @@ -527,7 +554,7 @@ cbrrr_parse_object(const uint8_t *buf, size_t len, PyObject **value, PyObject *c
);
parse_stack[sp].count -= 1;
} else { /* if we're currently parsing a map */
const u_int8_t *str;
const uint8_t *str;
size_t str_len;
size_t res = cbrrr_parse_raw_string(&buf[idx], len-idx, DCMT_TEXT_STRING, &str, &str_len);
if (res == (size_t)-1) {
Expand Down Expand Up @@ -1314,13 +1341,19 @@ cbrrr_encode_object(CbrrrBuf *buf, PyObject *obj_in, PyObject* cid_type, int atj
break;
}
uint64_t dub_int = ((union {uint64_t num; double dub;}){.dub=doubleval}).num;

/* hacky in-place endian-swap, the compiler should know what to do */
dub_int = ((dub_int << 8) & 0xFF00FF00FF00FF00ULL ) | ((dub_int >> 8) & 0x00FF00FF00FF00FFULL );
dub_int = ((dub_int << 16) & 0xFFFF0000FFFF0000ULL ) | ((dub_int >> 16) & 0x0000FFFF0000FFFFULL );
dub_int = (dub_int << 32) | ((dub_int >> 32) & 0xFFFFFFFFULL);

if (cbrrr_buf_write(buf, (uint8_t*)&dub_int, sizeof(dub_int)) < 0) {
uint8_t dub_be[8];

/* the compiler should be smart about emitting an endian swap if necessary */
dub_be[0] = (dub_int >> 56) & 0xff;
dub_be[1] = (dub_int >> 48) & 0xff;
dub_be[2] = (dub_int >> 40) & 0xff;
dub_be[3] = (dub_int >> 32) & 0xff;
dub_be[4] = (dub_int >> 24) & 0xff;
dub_be[5] = (dub_int >> 16) & 0xff;
dub_be[6] = (dub_int >> 8) & 0xff;
dub_be[7] = (dub_int >> 0) & 0xff;

if (cbrrr_buf_write(buf, dub_be, sizeof(dub_be)) < 0) {
break;
}
continue;
Expand Down
Loading