What happens
A configured storage quota never accounts for what is already stored, so it only ever rejects a single upload larger than the entire limit. An instance configured with a 10 GB quota accepts 10 GB, then another 10 GB, and keeps going.
Why
StorageProvider.usage() reports -1 on both backends.
FileSystemStorageProvider.usage() is getFileSize(Location.empty()), and getFileSize returns -1 for a DIRECTORY:
override fun getFileSize(location: Location): Result<Long, ErrorResponse> =
location.resolveWithRootDirectory()
.exists()
.map {
when (it.type()) {
FILE -> it.fileSize()
DIRECTORY -> -1
}
}
Nothing walks the tree, so a repository root has no size to report.
Both quota providers subtract that value:
// FixedQuota
override fun canHold(contentLength: Long): Result<Long, ErrorResponse> =
usage().map { usage -> maxSize - usage }.filter({ contentLength <= it }, { ... })
// PercentageQuota
usage().map { usage -> (rootDirectory.fileStore().usableSpace * maxPercentage).toLong() - usage }
With usage() == -1 the available space is maxSize + 1, constant, regardless of what the repository already holds.
S3StorageProvider is worse: usage() returns ok(-1) as well, but canHold is overridden to ok(Long.MAX_VALUE), so an S3 backed repository has no quota at all.
Impact
Operators who set a quota believe a limit is enforced when it is not. Nobody gains a permission they did not have, so this is a feature that silently does nothing rather than a way in, but a disk can still fill up against an explicit configuration.
Where it lives
reposilite-backend/src/main/kotlin/com/reposilite/storage/filesystem/FileSystemStorageProvider.kt, getFileSize and usage
reposilite-backend/src/main/kotlin/com/reposilite/storage/filesystem/FileSystemQuotaProviders.kt, FixedQuota.canHold and PercentageQuota.canHold
reposilite-backend/src/main/kotlin/com/reposilite/storage/s3/S3StorageProvider.kt, usage and canHold
What a fix has to consider
A correct usage() is a recursive directory walk on the filesystem and a paginated listing on S3, so it cannot run per request. It needs a cached value, refreshed on a schedule and invalidated on deploy, or the quota check has to move somewhere it can afford the cost. Whichever way it goes, canHold should fail loudly rather than silently pass when the backend cannot report a usage.
Notes
Found while reading this code for an unrelated change. Upstream Reposilite carries the same implementation.
What happens
A configured storage quota never accounts for what is already stored, so it only ever rejects a single upload larger than the entire limit. An instance configured with a 10 GB quota accepts 10 GB, then another 10 GB, and keeps going.
Why
StorageProvider.usage()reports-1on both backends.FileSystemStorageProvider.usage()isgetFileSize(Location.empty()), andgetFileSizereturns-1for aDIRECTORY:Nothing walks the tree, so a repository root has no size to report.
Both quota providers subtract that value:
With
usage() == -1the available space ismaxSize + 1, constant, regardless of what the repository already holds.S3StorageProvideris worse:usage()returnsok(-1)as well, butcanHoldis overridden took(Long.MAX_VALUE), so an S3 backed repository has no quota at all.Impact
Operators who set a quota believe a limit is enforced when it is not. Nobody gains a permission they did not have, so this is a feature that silently does nothing rather than a way in, but a disk can still fill up against an explicit configuration.
Where it lives
reposilite-backend/src/main/kotlin/com/reposilite/storage/filesystem/FileSystemStorageProvider.kt,getFileSizeandusagereposilite-backend/src/main/kotlin/com/reposilite/storage/filesystem/FileSystemQuotaProviders.kt,FixedQuota.canHoldandPercentageQuota.canHoldreposilite-backend/src/main/kotlin/com/reposilite/storage/s3/S3StorageProvider.kt,usageandcanHoldWhat a fix has to consider
A correct
usage()is a recursive directory walk on the filesystem and a paginated listing on S3, so it cannot run per request. It needs a cached value, refreshed on a schedule and invalidated on deploy, or the quota check has to move somewhere it can afford the cost. Whichever way it goes,canHoldshould fail loudly rather than silently pass when the backend cannot report a usage.Notes
Found while reading this code for an unrelated change. Upstream Reposilite carries the same implementation.