Alpine coarse_version is not monotonic for versions ending in a letter (like OpenSSL 1.1.1w)
- Dominant language
- Go
- Stars
- 2.9k
- Forks
- 369
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 149
Description
### Summary
`APK.coarse_version` in `osv/ecosystems/alpine.py` breaks the rule that
`ecosystems_base.py` says it must follow: "if v1 < v2, then
coarse_version(v1) <= coarse_version(v2)". It breaks for Alpine versions that
end in a letter, like OpenSSL's `1.1.1a` through `1.1.1w`, which is how OpenSSL
ships in Alpine. When this happens the stored `[coarse_min, coarse_max]` range
gets flipped, and the API's coarse pre-filter can miss versions that are
actually affected.
### How to reproduce
```py
from osv.ecosystems import alpine
apk = alpine.APK()
for v in ['1.1.1', '1.1.1a', '1.1.1k', '1.1.1w', '1.1.2']:
print(v, apk.coarse_version(v))
```
```
1.1.1 00:00000001.00000001.00000001
1.1.1a 00:00000001.00000001.00000000 <- patch drops to 0
1.1.1k 00:00000001.00000001.00000000
1.1.1w 00:00000001.00000001.00000000
1.1.2 00:00000001.00000001.00000002
```
The sort order is `1.1.1 < 1.1.1a < ... < 1.1.1w`, but `coarse(1.1.1)` is
greater than `coarse(1.1.1a)`.
### What causes it
`coarse_version` uses `separators_regex=r'[.]'` and `implicit_split=False`, so a
piece like `"1w"` never gets split into `1` and `w`. `"1w"` is not a decimal, so
the loop that reads integer parts stops there and the patch number falls back to
0.
### Why it matters
For a range with introduced `1.1.1` and fixed `1.1.1w`, `_get_coarse_min_max` in
`osv/models.py` stores `coarse_min = coarse(1.1.1) = ...1.1.1` and
`coarse_max = coarse(1.1.1w) = ...1.1.0`. So `coarse_min` ends up bigger than
`coarse_max`. The query filter in `gcp/api/server.py` uses
`coarse_min <= coarse(v) <= coarse_max`, so every affected version fails the
check and gets dropped.
One caveat: if the advisory also has an enumerated `versions` list, a second
`AffectedVersions` entity is built with `min`/`max` and can hide the miss at
query time. The miss is fully live for advisories that only have a range, and
the stored range is flipped either way.
### Why the existing test doesn't catch it
`apk_version_strategy` in `coarse_version_monotonicity_test.py` only builds
versions with `_rc`, `_p`, and `-r` suffixes. It never puts a bare letter at the
end, so the property test can't generate `1.1.1w` and passes today.
### This is not the known Go transitivity issue
The Go code already turns Alpine coarse versioning off:
// go/osv/ecosystem/apk.go
func (e apkEcosystem) Coarse(_ string) (string, error) {
// TODO(michaelkedar): semantic.AlpineVersion currently breaks transitivity
// rules (a < b, b < c, c < a) in some cases with invalid versions.
// Which makes coarse versions kinda broken.
return "", ErrCoarseNotSupported
}
and `FuzzAPKMonotonicity` is skipped, with Alpine commented out of
`coarse_large_test.go`.
That TODO is about transitivity breaking on invalid versions. This is a
different thing: monotonicity breaking on valid versions (`1.1.1a` through
`1.1.1w`). The Python path still has coarse versioning on, so Go fails safe here
and Python does not.
### Suggested fix
Two options, whichever you prefer:
- Set `implicit_split=True` in `APK.coarse_version`. It fixes the cases above
(letter versions map to the same coarse as their number base) and does not
break the existing `@example` cases.
- If Alpine coarse is meant to stay off, make the Python side fall back the way
Go does with `ErrCoarseNotSupported`.
Either way I would add letter suffixes to `apk_version_strategy` so this stays
covered.
Happy to work on this if you assign it to me.
Contributor guide
Research direction
Start with osv/ecosystems/alpine.py and coarse_version_monotonicity_test.py; read the invariant in ecosystems_base.py and run the existing Alpine monotonicity tests. Add coverage for letter-suffixed versions such as 1.1.1a and 1.1.1w, then verify their coarse versions remain ordered and affected ranges are not inverted.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, python
- Domain
- backend, testing-qa
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100