Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
609bc41e6f | ||
|
|
89999119dc | ||
|
|
71043329f1 | ||
|
|
ecaf293c4f | ||
|
|
09cb1fbca9 | ||
|
|
23620d0b7a | ||
|
|
351dbdfdca | ||
|
|
128159a717 | ||
|
|
33f713fa5d |
@@ -73,6 +73,9 @@ detroyejr:
|
|||||||
display_name: Jonathan De Troye
|
display_name: Jonathan De Troye
|
||||||
github: detroyejr
|
github: detroyejr
|
||||||
|
|
||||||
|
edef:
|
||||||
|
github: edef1c
|
||||||
|
|
||||||
edolstra:
|
edolstra:
|
||||||
display_name: Eelco Dolstra
|
display_name: Eelco Dolstra
|
||||||
github: edolstra
|
github: edolstra
|
||||||
@@ -249,6 +252,9 @@ rootile:
|
|||||||
display_name: rootile (Rutile)
|
display_name: rootile (Rutile)
|
||||||
forgejo: rootile
|
forgejo: rootile
|
||||||
|
|
||||||
|
sandydoo:
|
||||||
|
github: sandydoo
|
||||||
|
|
||||||
seppel3210:
|
seppel3210:
|
||||||
github: Seppel3210
|
github: Seppel3210
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,35 @@
|
|||||||
# Lix 2.95 "Kakigōri" (2026-03-13)
|
# Lix 2.95 "Kakigōri" (2026-03-13)
|
||||||
|
# Lix 2.95.2 (2026-05-04)
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Fix unsigned overflow leading to out-of-band write in the NAR parser [cl/5550](https://gerrit.lix.systems/c/lix/+/5550)
|
||||||
|
|
||||||
|
The NAR parser contained an unsigned integer overflow that could be used by an
|
||||||
|
attacker to write arbitrary data to an unknown memory location and possibly
|
||||||
|
achieve code execution. A successful attack on the system-wide Lix daemon
|
||||||
|
could lead to privilege escalation to root. Any process that involves NAR
|
||||||
|
serialization could trigger this issue, including (but not limited to)
|
||||||
|
|
||||||
|
- local user interaction, whether the users are trusted or untrusted
|
||||||
|
- malicious substituters sending malformed NARs
|
||||||
|
- remote builders sending malformed build results
|
||||||
|
- remote daemons sending malformed inputs when requesting remote builds
|
||||||
|
|
||||||
|
Successful attacks using this bug require ASLR weakening of some sort, whether
|
||||||
|
by architecture constraints (e.g. on 32 bit systems, where little randomization
|
||||||
|
is possible) or system configuration (e.g. low ASLR entropy when loading
|
||||||
|
libraries), and millions of attempts. Local attacks can be mounted in less than
|
||||||
|
an hour. Remote builds typically require a fresh SSH connection for each build
|
||||||
|
and are thus less susceptible. Only one attempt can be made by substituters for
|
||||||
|
every build using substituters, they are thus not a likely vector for attacks.
|
||||||
|
|
||||||
|
At the time of writing, MITRE has not assigned this a CVE yet.
|
||||||
|
|
||||||
|
Many thanks to [eldritch horrors](https://git.lix.systems/pennae), [Raito Bezarius](https://git.lix.systems/raito), [edef](https://github.com/edef1c), and [sandydoo](https://github.com/sandydoo) for this.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Lix 2.95.1 (2026-03-19)
|
# Lix 2.95.1 (2026-03-19)
|
||||||
## Fixes
|
## Fixes
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -867,8 +867,7 @@ void NixRepl::initBuiltinCommands()
|
|||||||
[](NixRepl & repl, const std::string & arg) {
|
[](NixRepl & repl, const std::string & arg) {
|
||||||
Value v = repl.evalString(arg);
|
Value v = repl.evalString(arg);
|
||||||
Value f = repl.evalString(
|
Value f = repl.evalString(
|
||||||
R""("drv: (import <nixpkgs> {}).runCommand "shell") ""
|
R"(drv: (import <nixpkgs> {}).runCommand "shell" { buildInputs = [ drv ]; } "")"
|
||||||
R""({ buildInputs = [ drv ]; } "")""
|
|
||||||
);
|
);
|
||||||
Value result = repl.state.callFunction(f, v, PosIdx());
|
Value result = repl.state.callFunction(f, v, PosIdx());
|
||||||
|
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ try {
|
|||||||
|
|
||||||
auto * buildIdDir = std::get_if<nar_index::Directory>(&narIndex);
|
auto * buildIdDir = std::get_if<nar_index::Directory>(&narIndex);
|
||||||
for (auto subdir : { "lib", "debug", ".build-id" }) {
|
for (auto subdir : { "lib", "debug", ".build-id" }) {
|
||||||
|
if (!buildIdDir) break;
|
||||||
// get returns nullptr subdir does not exist, and std::get_if propagates it.
|
// get returns nullptr subdir does not exist, and std::get_if propagates it.
|
||||||
buildIdDir = std::get_if<nar_index::Directory>(get(buildIdDir->contents, subdir));
|
buildIdDir = std::get_if<nar_index::Directory>(get(buildIdDir->contents, subdir));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ struct Parser
|
|||||||
buffer.clear(); \
|
buffer.clear(); \
|
||||||
std::move(str); \
|
std::move(str); \
|
||||||
})
|
})
|
||||||
#define READ_STRING() READ_STRING_LIMITED(std::numeric_limits<size_t>::max())
|
#define READ_STRING() READ_STRING_LIMITED(1048576)
|
||||||
#define READ_PADDING(size) \
|
#define READ_PADDING(size) \
|
||||||
do { \
|
do { \
|
||||||
if ((size) % 8) { \
|
if ((size) % 8) { \
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ def setup_creds(env: RelengEnvironment):
|
|||||||
key = keys.get_ephemeral_key(env)
|
key = keys.get_ephemeral_key(env)
|
||||||
$AWS_SECRET_ACCESS_KEY = key.secret_key
|
$AWS_SECRET_ACCESS_KEY = key.secret_key
|
||||||
$AWS_ACCESS_KEY_ID = key.id
|
$AWS_ACCESS_KEY_ID = key.id
|
||||||
$AWS_DEFAULT_REGION = 'garage'
|
$AWS_DEFAULT_REGION = env.s3_region
|
||||||
$AWS_ENDPOINT_URL = env.s3_endpoint
|
$AWS_ENDPOINT_URL = env.s3_endpoint
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+20
-11
@@ -5,11 +5,12 @@ import functools
|
|||||||
import subprocess
|
import subprocess
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
|
||||||
S3_HOST = 's3.lix.systems'
|
S3_HOST = 's3-admin.afnix.fr'
|
||||||
|
S3_USER = 'lix-releng'
|
||||||
|
|
||||||
DEFAULT_STORE_URI_BITS = {
|
DEFAULT_STORE_URI_BITS = {
|
||||||
'region': 'garage',
|
'region': 'global',
|
||||||
'endpoint': 's3.lix.systems',
|
'endpoint': 's3.afnix.fr',
|
||||||
'want-mass-query': 'true',
|
'want-mass-query': 'true',
|
||||||
'write-nar-listing': 'true',
|
'write-nar-listing': 'true',
|
||||||
'ls-compression': 'zstd',
|
'ls-compression': 'zstd',
|
||||||
@@ -54,7 +55,9 @@ class RelengEnvironment:
|
|||||||
git_repo: Callable[[], str]
|
git_repo: Callable[[], str]
|
||||||
git_repo_is_gerrit: bool
|
git_repo_is_gerrit: bool
|
||||||
s3_endpoint: str
|
s3_endpoint: str
|
||||||
|
s3_region: str
|
||||||
s3_ssh_host: str | None
|
s3_ssh_host: str | None
|
||||||
|
s3_ssh_user: str | None
|
||||||
|
|
||||||
docker_targets: list[DockerTarget]
|
docker_targets: list[DockerTarget]
|
||||||
|
|
||||||
@@ -86,17 +89,19 @@ LOCAL = RelengEnvironment(
|
|||||||
git_repo_is_gerrit=False,
|
git_repo_is_gerrit=False,
|
||||||
docker_targets=[],
|
docker_targets=[],
|
||||||
s3_endpoint = 'http://localhost:3900',
|
s3_endpoint = 'http://localhost:3900',
|
||||||
|
s3_region = 'garage',
|
||||||
s3_ssh_host = None,
|
s3_ssh_host = None,
|
||||||
|
s3_ssh_user = None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
STAGING = RelengEnvironment(
|
STAGING = RelengEnvironment(
|
||||||
name='staging',
|
name='staging',
|
||||||
colour=functools.partial(sgr, GREEN),
|
colour=functools.partial(sgr, GREEN),
|
||||||
docs_bucket='s3://staging-docs',
|
docs_bucket='s3://docs.staging.lix.systems',
|
||||||
cache_bucket='s3://staging-cache',
|
cache_bucket='s3://cache.staging.lix.systems',
|
||||||
cache_store_overlay={'secret-key': 'staging.key'},
|
cache_store_overlay={'secret-key': 'staging.key'},
|
||||||
releases_bucket='s3://staging-releases',
|
releases_bucket='s3://releases.staging.lix.systems',
|
||||||
git_repo=lambda: 'ssh://git@git.lix.systems/lix-project/lix-releng-staging',
|
git_repo=lambda: 'ssh://git@git.lix.systems/lix-project/lix-releng-staging',
|
||||||
git_repo_is_gerrit=False,
|
git_repo_is_gerrit=False,
|
||||||
docker_targets=[
|
docker_targets=[
|
||||||
@@ -106,8 +111,10 @@ STAGING = RelengEnvironment(
|
|||||||
DockerTarget('ghcr.io/lix-project/lix-releng-staging',
|
DockerTarget('ghcr.io/lix-project/lix-releng-staging',
|
||||||
tags=['{version}', '{major}']),
|
tags=['{version}', '{major}']),
|
||||||
],
|
],
|
||||||
s3_endpoint = 'https://s3.lix.systems',
|
s3_endpoint = 'https://s3.afnix.fr',
|
||||||
|
s3_region = 'garage',
|
||||||
s3_ssh_host = S3_HOST,
|
s3_ssh_host = S3_HOST,
|
||||||
|
s3_ssh_user = S3_USER,
|
||||||
)
|
)
|
||||||
|
|
||||||
GERRIT_REMOTE_RE = re.compile(r'^ssh://(\w+@)?gerrit.lix.systems:2022/lix$')
|
GERRIT_REMOTE_RE = re.compile(r'^ssh://(\w+@)?gerrit.lix.systems:2022/lix$')
|
||||||
@@ -127,13 +134,13 @@ def guess_gerrit_remote():
|
|||||||
PROD = RelengEnvironment(
|
PROD = RelengEnvironment(
|
||||||
name='production',
|
name='production',
|
||||||
colour=functools.partial(sgr, RED),
|
colour=functools.partial(sgr, RED),
|
||||||
docs_bucket='s3://docs',
|
docs_bucket='s3://docs.lix.systems',
|
||||||
cache_bucket='s3://cache',
|
cache_bucket='s3://cache.lix.systems',
|
||||||
# FIXME: we should decrypt this with age into a tempdir in the future, but
|
# FIXME: we should decrypt this with age into a tempdir in the future, but
|
||||||
# the issue is how to deal with the recipients file. For now, we should
|
# the issue is how to deal with the recipients file. For now, we should
|
||||||
# just delete it after doing a release.
|
# just delete it after doing a release.
|
||||||
cache_store_overlay={'secret-key': 'prod.key'},
|
cache_store_overlay={'secret-key': 'prod.key'},
|
||||||
releases_bucket='s3://releases',
|
releases_bucket='s3://releases.lix.systems',
|
||||||
git_repo=guess_gerrit_remote,
|
git_repo=guess_gerrit_remote,
|
||||||
git_repo_is_gerrit=True,
|
git_repo_is_gerrit=True,
|
||||||
docker_targets=[
|
docker_targets=[
|
||||||
@@ -142,8 +149,10 @@ PROD = RelengEnvironment(
|
|||||||
tags=['{version}', '{major}']),
|
tags=['{version}', '{major}']),
|
||||||
DockerTarget('ghcr.io/lix-project/lix', tags=['{version}', '{major}']),
|
DockerTarget('ghcr.io/lix-project/lix', tags=['{version}', '{major}']),
|
||||||
],
|
],
|
||||||
s3_endpoint = 'https://s3.lix.systems',
|
s3_endpoint = 'https://s3.afnix.fr',
|
||||||
|
s3_region = 'global',
|
||||||
s3_ssh_host = S3_HOST,
|
s3_ssh_host = S3_HOST,
|
||||||
|
s3_ssh_user = S3_USER,
|
||||||
)
|
)
|
||||||
|
|
||||||
ENVIRONMENTS = {
|
ENVIRONMENTS = {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 Jade Lovelace
|
# SPDX-FileCopyrightText: 2024 Jade Lovelace
|
||||||
|
# SPDX-FileCopyrightText: 2026 Yureka Lilian <yureka@cyberchaos.dev>
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import datetime
|
import datetime
|
||||||
import dataclasses
|
|
||||||
import re
|
import re
|
||||||
from typing import Any, Literal, Optional
|
from typing import Any
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
@@ -14,27 +14,34 @@ import logging
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.setLevel(logging.INFO)
|
log.setLevel(logging.INFO)
|
||||||
|
|
||||||
fmt = logging.Formatter('{asctime} {levelname} {name}: {message}',
|
fmt = logging.Formatter(
|
||||||
datefmt='%b %d %H:%M:%S',
|
"{asctime} {levelname} {name}: {message}",
|
||||||
style='{')
|
datefmt="%b %d %H:%M:%S",
|
||||||
|
style="{",
|
||||||
|
)
|
||||||
|
|
||||||
if not any(isinstance(h, logging.StreamHandler) for h in log.handlers):
|
if not any(isinstance(h, logging.StreamHandler) for h in log.handlers):
|
||||||
hand = logging.StreamHandler()
|
hand = logging.StreamHandler()
|
||||||
hand.setFormatter(fmt)
|
hand.setFormatter(fmt)
|
||||||
log.addHandler(hand)
|
log.addHandler(hand)
|
||||||
|
|
||||||
API_BASE = os.environ.get('GARAGE_ADMIN_API_BASE', 'http://localhost:3903')
|
API_BASE = os.environ.get("GARAGE_ADMIN_API_BASE", "http://localhost:3903")
|
||||||
API_KEY = os.environ['GARAGE_ADMIN_TOKEN']
|
API_KEY = os.environ["GARAGE_ADMIN_TOKEN"]
|
||||||
|
|
||||||
|
BUCKET_REGEX_STR = os.environ.get("BUCKET_REGEX", ".*")
|
||||||
|
BUCKET_REGEX = re.compile(BUCKET_REGEX_STR)
|
||||||
|
|
||||||
|
|
||||||
def api(method, endpoint: str, resp_json=True, **kwargs) -> Any:
|
def api(method, endpoint: str, resp_json=True, **kwargs) -> Any:
|
||||||
log.info('http %s %s', method, endpoint)
|
log.info("http %s %s", method, endpoint)
|
||||||
if not endpoint.startswith('https'):
|
if not endpoint.startswith("https"):
|
||||||
endpoint = API_BASE + endpoint
|
endpoint = API_BASE + endpoint
|
||||||
resp = requests.request(method,
|
resp = requests.request(
|
||||||
endpoint,
|
method,
|
||||||
headers={'Authorization': f'Bearer {API_KEY}'},
|
endpoint,
|
||||||
**kwargs)
|
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
if resp_json:
|
if resp_json:
|
||||||
return resp.json()
|
return resp.json()
|
||||||
@@ -42,97 +49,64 @@ def api(method, endpoint: str, resp_json=True, **kwargs) -> Any:
|
|||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
def get_bucket_id(bucket_name: str) -> str:
|
||||||
class Key:
|
resp: dict = api(
|
||||||
name: str
|
"GET", "/v2/GetBucketInfo", params={"globalAlias": bucket_name}
|
||||||
id: str
|
)
|
||||||
secret_key: Optional[str] = None
|
return resp["id"]
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
DATEFMT = "%Y%m%d%H%M%S"
|
||||||
class Bucket:
|
|
||||||
id: str
|
|
||||||
|
|
||||||
|
|
||||||
def keys() -> list[Key]:
|
|
||||||
data: list[dict] = api('GET', '/v1/key?list')
|
|
||||||
return [Key(name=k['name'], id=k['id']) for k in data]
|
|
||||||
|
|
||||||
|
|
||||||
def delete_key(key: Key):
|
|
||||||
api('DELETE', '/v1/key', resp_json=False, params={'id': key.id})
|
|
||||||
|
|
||||||
|
|
||||||
def create_key(name: str) -> Key:
|
|
||||||
resp: dict = api('POST', '/v1/key', json={'name': name})
|
|
||||||
return Key(name=resp['name'],
|
|
||||||
id=resp['accessKeyId'],
|
|
||||||
secret_key=resp['secretAccessKey'])
|
|
||||||
|
|
||||||
|
|
||||||
AccessType = Literal['read'] | Literal['write'] | Literal['owner']
|
|
||||||
|
|
||||||
|
|
||||||
def get_bucket(bucket_name: str) -> Bucket:
|
|
||||||
resp: dict = api('GET', '/v1/bucket', params={'globalAlias': bucket_name})
|
|
||||||
return Bucket(resp['id'])
|
|
||||||
|
|
||||||
|
|
||||||
def grant(bucket: Bucket, access_types: list[AccessType], key: Key):
|
|
||||||
access_types_dict = {k: True for k in access_types}
|
|
||||||
api('POST',
|
|
||||||
'/v1/bucket/allow',
|
|
||||||
json={
|
|
||||||
'bucketId': bucket.id,
|
|
||||||
'accessKeyId': key.id,
|
|
||||||
'permissions': access_types_dict,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
KEY_RE = re.compile(r'^.*ephemeral-(\d{14})$')
|
|
||||||
DATEFMT = '%Y%m%d%H%M%S'
|
|
||||||
|
|
||||||
|
|
||||||
def expired_keys(older_than: datetime.datetime) -> list[Key]:
|
|
||||||
ret = []
|
|
||||||
for key in keys():
|
|
||||||
if m := KEY_RE.match(key.name):
|
|
||||||
date = datetime.datetime.strptime(m.group(1), DATEFMT)
|
|
||||||
date = date.astimezone(datetime.UTC)
|
|
||||||
print(date)
|
|
||||||
if date < older_than:
|
|
||||||
ret.append(key)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def do_new(args):
|
def do_new(args):
|
||||||
buckets = [get_bucket(b) for b in args.buckets]
|
for b in args.buckets:
|
||||||
|
if not BUCKET_REGEX.match(b):
|
||||||
|
print(f"Bucket {b} not in allowed buckeds '{BUCKET_REGEX_STR}'")
|
||||||
|
exit(1)
|
||||||
|
bucket_ids = [get_bucket_id(b) for b in args.buckets]
|
||||||
|
|
||||||
def optional(s: str, whether) -> list[str]:
|
key_name = args.name + "-" if args.name else ""
|
||||||
if whether:
|
expiration = datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(
|
||||||
return [s]
|
seconds=args.age_secs
|
||||||
else:
|
)
|
||||||
return []
|
key_name += "ephemeral-" + expiration.strftime(DATEFMT)
|
||||||
|
|
||||||
access_types: list[AccessType] = optional('read', args.read) + optional(
|
key_resp: dict = api(
|
||||||
'write', args.write) + optional('owner', args.owner) # type: ignore
|
"POST",
|
||||||
|
"/v2/CreateKey",
|
||||||
|
json={
|
||||||
|
"name": key_name,
|
||||||
|
"expiration": expiration.isoformat(),
|
||||||
|
"neverExpires": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
key_name = args.name + '-' if args.name else ''
|
for b in bucket_ids:
|
||||||
key_name += "ephemeral-" + (
|
api(
|
||||||
datetime.datetime.now(tz=datetime.UTC) +
|
"POST",
|
||||||
datetime.timedelta(seconds=args.age_secs)).strftime(DATEFMT)
|
"/v2/AllowBucketKey",
|
||||||
|
json={
|
||||||
|
"accessKeyId": key_resp["accessKeyId"],
|
||||||
|
"bucketId": b,
|
||||||
|
"permissions": {
|
||||||
|
"read": args.read,
|
||||||
|
"write": args.write,
|
||||||
|
"owner": args.owner,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
k = create_key(key_name)
|
print(
|
||||||
for b in buckets:
|
json.dumps(
|
||||||
grant(b, access_types, k)
|
{
|
||||||
|
"name": key_resp["name"],
|
||||||
print(json.dumps(dataclasses.asdict(k), indent=2))
|
"id": key_resp["accessKeyId"],
|
||||||
|
"secret_key": key_resp["secretAccessKey"],
|
||||||
|
},
|
||||||
def do_clean(args):
|
indent=2,
|
||||||
older_than = datetime.datetime.now(tz=datetime.UTC)
|
)
|
||||||
for key in expired_keys(older_than):
|
)
|
||||||
delete_key(key)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -148,28 +122,27 @@ def main():
|
|||||||
|
|
||||||
new = sps.add_parser("new", help="Make an ephemeral key")
|
new = sps.add_parser("new", help="Make an ephemeral key")
|
||||||
new.add_argument("--name", help="Name prefix for the key")
|
new.add_argument("--name", help="Name prefix for the key")
|
||||||
new.add_argument("--read",
|
new.add_argument(
|
||||||
action="store_true",
|
"--read", action="store_true", help="Grant read access to buckets"
|
||||||
help="Grant read access to buckets")
|
)
|
||||||
new.add_argument("--write",
|
new.add_argument(
|
||||||
action="store_true",
|
"--write", action="store_true", help="Grant write access to buckets"
|
||||||
help="Grant write access to buckets")
|
)
|
||||||
new.add_argument("--owner",
|
new.add_argument(
|
||||||
action="store_true",
|
"--owner", action="store_true", help="Grant owner access to buckets"
|
||||||
help="Grant owner access to buckets")
|
)
|
||||||
new.add_argument("--age-secs",
|
new.add_argument(
|
||||||
type=int,
|
"--age-secs",
|
||||||
required=True,
|
type=int,
|
||||||
help="Maximum key lifetime in seconds")
|
required=True,
|
||||||
new.add_argument("buckets", nargs='*', help="Buckets to grant access to")
|
help="Maximum key lifetime in seconds",
|
||||||
|
)
|
||||||
|
new.add_argument("buckets", nargs="*", help="Buckets to grant access to")
|
||||||
new.set_defaults(cmd=do_new)
|
new.set_defaults(cmd=do_new)
|
||||||
|
|
||||||
clean = sps.add_parser("clean", help="Clean up old keys")
|
|
||||||
clean.set_defaults(cmd=do_clean)
|
|
||||||
|
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
args.cmd(args)
|
args.cmd(args)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ def get_ephemeral_key(
|
|||||||
env.docs_bucket.removeprefix('s3://'),
|
env.docs_bucket.removeprefix('s3://'),
|
||||||
]
|
]
|
||||||
if env.s3_ssh_host is not None:
|
if env.s3_ssh_host is not None:
|
||||||
command = ['ssh', '-l', 'root', env.s3_ssh_host, *command]
|
command = ['ssh', f'{env.s3_ssh_user}@{env.s3_ssh_host}', *command]
|
||||||
output = subprocess.check_output(command)
|
output = subprocess.check_output(command)
|
||||||
d = json.loads(output.decode())
|
d = json.loads(output.decode())
|
||||||
return environment.S3Credentials(name=d['name'],
|
return environment.S3Credentials(name=d['name'],
|
||||||
|
|||||||
@@ -521,4 +521,27 @@ INSTANTIATE_TEST_SUITE_P(
|
|||||||
concat({header, make_directory({{"DE", make_file(false, "meow")}, {"de", make_file(false, "mrrp")}})})
|
concat({header, make_directory({{"DE", make_file(false, "meow")}, {"de", make_file(false, "mrrp")}})})
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
TEST_F(NarTest, stringSizeLimit)
|
||||||
|
{
|
||||||
|
GeneratorSource source([]() -> Generator<Bytes> {
|
||||||
|
const char preamble[] =
|
||||||
|
"\x0d\x00\x00\x00\x00\x00\x00\x00nix-archive-1\x00\x00\x00"
|
||||||
|
"\x01\x00\x00\x00\x00\x00\x00\x00(\x00\x00\x00\x00\x00\x00\x00"
|
||||||
|
"\x04\x00\x00\x00\x00\x00\x00\x00type\x00\x00\x00\x00";
|
||||||
|
co_yield Bytes{preamble, sizeof(preamble) - 1};
|
||||||
|
// the nar parser keeps all strings in a buffer with the 8 byte length prefix in front.
|
||||||
|
// sufficiently large strings overflowed caused the buffer size calculation to overflow
|
||||||
|
// and thus allowed out-of-bounds writes in the daemon and potentially privesc to root.
|
||||||
|
co_yield Bytes{"\xf7\xff\xff\xff\xff\xff\xff\xff", 8};
|
||||||
|
// overflow would happen while reading data
|
||||||
|
while (true) {
|
||||||
|
co_yield Bytes{"foo-", 4};
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
|
||||||
|
auto parser = nar::parse(source);
|
||||||
|
|
||||||
|
ASSERT_THROW(parser.next(), SerialisationError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "2.95.1",
|
"version": "2.95.2",
|
||||||
"official_release": true,
|
"official_release": true,
|
||||||
"release_name": "Kakigōri"
|
"release_name": "Kakigōri"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user