Fix multiple security vulnerabilities across C, Python, and Go code
Timing-safe comparisons: - crypto.c: Replace memcmp with CRYPTO_memcmp for Secret equality, require equal lengths before comparing - remote_control.py: Constant-time password lookup to avoid leaking valid passwords via dict hash timing - file_transmission.py: Use hmac.compare_digest for bypass token comparison instead of == Memory safety: - child-monitor.c: Fix inverted condition in write_to_peer that prevented memmove from ever executing on partial writes - ibus_glfw.c: Null-terminate IBUS_ADDRESS copy to prevent string overread when strlen >= PATH_MAX - x11_window.c: Add NULL checks after realloc in clipboard/DnD data handling (two sites) - dnd.c: Cap accepted_mimes at 1MB to prevent unbounded growth, fix realloc to not lose the original pointer on failure - png-reader.c: Cast to size_t before multiplication to prevent integer overflow on 32-bit platforms Secrets hygiene: - disk-cache.c: Zero encryption_key with explicit_bzero before free Tar extraction hardening: - tar.go: Validate hardlink targets against destination prefix to prevent writing outside extraction directory - tar.go: Strip setuid/setgid/sticky bits from extracted files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0619c7e435
commit
b39f88c6a2
10 changed files with 44 additions and 19 deletions
4
glfw/ibus_glfw.c
vendored
4
glfw/ibus_glfw.c
vendored
|
|
@ -287,7 +287,9 @@ get_ibus_address_file_name(void) {
|
||||||
addr = getenv("IBUS_ADDRESS");
|
addr = getenv("IBUS_ADDRESS");
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
if (addr && addr[0]) {
|
if (addr && addr[0]) {
|
||||||
memcpy(ans, addr, GLFW_MIN(strlen(addr), sizeof(ans)));
|
size_t len = GLFW_MIN(strlen(addr), sizeof(ans) - 1);
|
||||||
|
memcpy(ans, addr, len);
|
||||||
|
ans[len] = '\0';
|
||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
const char* disp_num = NULL;
|
const char* disp_num = NULL;
|
||||||
|
|
|
||||||
8
glfw/x11_window.c
vendored
8
glfw/x11_window.c
vendored
|
|
@ -937,7 +937,9 @@ get_clipboard_data(const _GLFWClipboardData *cd, const char *mime, char **data)
|
||||||
if (!chunk.sz) break;
|
if (!chunk.sz) break;
|
||||||
if (cap < sz + chunk.sz) {
|
if (cap < sz + chunk.sz) {
|
||||||
cap = MAX(cap * 2, sz + 4 * chunk.sz);
|
cap = MAX(cap * 2, sz + 4 * chunk.sz);
|
||||||
buf = realloc(buf, cap * sizeof(buf[0]));
|
char *new_buf = realloc(buf, cap * sizeof(buf[0]));
|
||||||
|
if (!new_buf) { free(buf); *data = NULL; cd->get_data(NULL, iter, cd->ctype); return 0; }
|
||||||
|
buf = new_buf;
|
||||||
}
|
}
|
||||||
memcpy(buf + sz, chunk.data, chunk.sz);
|
memcpy(buf + sz, chunk.data, chunk.sz);
|
||||||
sz += chunk.sz;
|
sz += chunk.sz;
|
||||||
|
|
@ -3636,7 +3638,9 @@ write_chunk(void *object, const char *data, size_t sz) {
|
||||||
if (data) {
|
if (data) {
|
||||||
if (cw->cap < cw->sz + sz) {
|
if (cw->cap < cw->sz + sz) {
|
||||||
cw->cap = MAX(cw->cap * 2, cw->sz + 8*sz);
|
cw->cap = MAX(cw->cap * 2, cw->sz + 8*sz);
|
||||||
cw->buf = realloc(cw->buf, cw->cap * sizeof(cw->buf[0]));
|
char *new_buf = realloc(cw->buf, cw->cap * sizeof(cw->buf[0]));
|
||||||
|
if (!new_buf) return false;
|
||||||
|
cw->buf = new_buf;
|
||||||
}
|
}
|
||||||
memcpy(cw->buf + cw->sz, data, sz);
|
memcpy(cw->buf + cw->sz, data, sz);
|
||||||
cw->sz += sz;
|
cw->sz += sz;
|
||||||
|
|
|
||||||
|
|
@ -1940,7 +1940,7 @@ write_to_peer(Peer *peer) {
|
||||||
else if (n < 0) {
|
else if (n < 0) {
|
||||||
if (errno != EINTR) { log_error("write() to peer socket failed with error: %s", strerror(errno)); peer->write.used = 0; peer->write.failed = true; }
|
if (errno != EINTR) { log_error("write() to peer socket failed with error: %s", strerror(errno)); peer->write.used = 0; peer->write.failed = true; }
|
||||||
} else {
|
} else {
|
||||||
if ((size_t)n > peer->write.used) memmove(peer->write.data, peer->write.data + n, peer->write.used - n);
|
if ((size_t)n < peer->write.used) memmove(peer->write.data, peer->write.data + n, peer->write.used - n);
|
||||||
peer->write.used -= n;
|
peer->write.used -= n;
|
||||||
}
|
}
|
||||||
talk_mutex(unlock);
|
talk_mutex(unlock);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
#include <openssl/pem.h>
|
#include <openssl/pem.h>
|
||||||
#include <openssl/bio.h>
|
#include <openssl/bio.h>
|
||||||
#include <openssl/rand.h>
|
#include <openssl/rand.h>
|
||||||
|
#include <openssl/crypto.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <structmember.h>
|
#include <structmember.h>
|
||||||
|
|
||||||
|
|
@ -72,8 +73,8 @@ dealloc_secret(Secret *self) {
|
||||||
|
|
||||||
static int
|
static int
|
||||||
__eq__(Secret *a, Secret *b) {
|
__eq__(Secret *a, Secret *b) {
|
||||||
const size_t l = a->secret_len < b->secret_len ? a->secret_len : b->secret_len;
|
if (a->secret_len != b->secret_len) return 0;
|
||||||
return memcmp(a->secret, b->secret, l) == 0;
|
return CRYPTO_memcmp(a->secret, b->secret, a->secret_len) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Py_ssize_t
|
static Py_ssize_t
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include "cross-platform-random.h"
|
#include "cross-platform-random.h"
|
||||||
#include <structmember.h>
|
#include <structmember.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
@ -40,7 +41,7 @@ static uint64_t key_hash(KEY_TY k);
|
||||||
#define HASH_FN key_hash
|
#define HASH_FN key_hash
|
||||||
static bool keys_are_equal(CacheKey a, CacheKey b) { return a.hash_keylen == b.hash_keylen && memcmp(a.hash_key, b.hash_key, a.hash_keylen) == 0; }
|
static bool keys_are_equal(CacheKey a, CacheKey b) { return a.hash_keylen == b.hash_keylen && memcmp(a.hash_key, b.hash_key, a.hash_keylen) == 0; }
|
||||||
#define CMPR_FN keys_are_equal
|
#define CMPR_FN keys_are_equal
|
||||||
static void free_cache_value(CacheValue *cv) { free(cv->data); cv->data = NULL; free(cv); }
|
static void free_cache_value(CacheValue *cv) { explicit_bzero(cv->encryption_key, sizeof(cv->encryption_key)); free(cv->data); cv->data = NULL; free(cv); }
|
||||||
static void free_cache_key(CacheKey cv) { free(cv.hash_key); cv.hash_key = NULL; }
|
static void free_cache_key(CacheKey cv) { free(cv.hash_key); cv.hash_key = NULL; }
|
||||||
#define KEY_DTOR_FN free_cache_key
|
#define KEY_DTOR_FN free_cache_key
|
||||||
#define VAL_DTOR_FN free_cache_value
|
#define VAL_DTOR_FN free_cache_value
|
||||||
|
|
|
||||||
11
kitty/dnd.c
11
kitty/dnd.c
|
|
@ -308,11 +308,12 @@ drop_set_status(Window *w, int operation, const char *payload, size_t payload_sz
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (payload_sz) {
|
if (payload_sz) {
|
||||||
w->drop.accepted_mimes = realloc(w->drop.accepted_mimes, w->drop.accepted_mimes_sz + payload_sz + 2);
|
if (w->drop.accepted_mimes_sz + payload_sz > 1024 * 1024) return;
|
||||||
if (w->drop.accepted_mimes) {
|
char *new_buf = realloc(w->drop.accepted_mimes, w->drop.accepted_mimes_sz + payload_sz + 2);
|
||||||
memcpy(w->drop.accepted_mimes + w->drop.accepted_mimes_sz, payload, payload_sz);
|
if (!new_buf) return;
|
||||||
w->drop.accepted_mimes_sz += payload_sz;
|
w->drop.accepted_mimes = new_buf;
|
||||||
}
|
memcpy(w->drop.accepted_mimes + w->drop.accepted_mimes_sz, payload, payload_sz);
|
||||||
|
w->drop.accepted_mimes_sz += payload_sz;
|
||||||
}
|
}
|
||||||
if (!more) {
|
if (!more) {
|
||||||
w->drop.accept_in_progress = false;
|
w->drop.accept_in_progress = false;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
|
import hmac
|
||||||
import inspect
|
import inspect
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
|
@ -572,12 +573,12 @@ def check_bypass(password: str, request_id: str, bypass_data: str) -> bool:
|
||||||
delta = time_ns() - int(timestamp)
|
delta = time_ns() - int(timestamp)
|
||||||
if abs(delta) > 5 * 60 * 1e9:
|
if abs(delta) > 5 * 60 * 1e9:
|
||||||
return False
|
return False
|
||||||
return payload == f'{request_id};{password}'
|
return hmac.compare_digest(payload, f'{request_id};{password}')
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
log_error(f'Invalid file transmission bypass data received: {err}')
|
log_error(f'Invalid file transmission bypass data received: {err}')
|
||||||
return False
|
return False
|
||||||
elif protocol == 'sha256':
|
elif protocol == 'sha256':
|
||||||
return (encode_bypass(request_id, password) == bypass_data) if password else False
|
return hmac.compare_digest(encode_bypass(request_id, password), bypass_data) if password else False
|
||||||
else:
|
else:
|
||||||
log_error(f'Invalid file transmission bypass data received with protocol: {protocol}')
|
log_error(f'Invalid file transmission bypass data received with protocol: {protocol}')
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz, int max_im
|
||||||
png_read_update_info(png, info);
|
png_read_update_info(png, info);
|
||||||
|
|
||||||
png_uint_32 rowbytes = png_get_rowbytes(png, info);
|
png_uint_32 rowbytes = png_get_rowbytes(png, info);
|
||||||
d->sz = sizeof(png_byte) * rowbytes * d->height;
|
d->sz = (size_t)rowbytes * (size_t)d->height;
|
||||||
d->decompressed = malloc(d->sz + 16);
|
d->decompressed = malloc(d->sz + 16);
|
||||||
if (d->decompressed == NULL) ABRT(ENOMEM, "Out of memory allocating decompression buffer for PNG");
|
if (d->decompressed == NULL) ABRT(ENOMEM, "Out of memory allocating decompression buffer for PNG");
|
||||||
d->row_pointers = malloc(d->height * sizeof(png_bytep));
|
d->row_pointers = malloc(d->height * sizeof(png_bytep));
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import hmac
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
@ -103,6 +104,14 @@ def fnmatch_pattern(pat: str) -> 're.Pattern[str]':
|
||||||
return re.compile(translate(pat))
|
return re.compile(translate(pat))
|
||||||
|
|
||||||
|
|
||||||
|
def _ct_password_lookup(passwords: dict[str, Any], pw: str) -> Any:
|
||||||
|
result = None
|
||||||
|
for k, v in passwords.items():
|
||||||
|
if hmac.compare_digest(k, pw):
|
||||||
|
result = v
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def remote_control_allowed(
|
def remote_control_allowed(
|
||||||
pcmd: dict[str, Any], remote_control_passwords: dict[str, Sequence[str]] | None,
|
pcmd: dict[str, Any], remote_control_passwords: dict[str, Sequence[str]] | None,
|
||||||
window: Optional['Window'], extra_data: dict[str, Any]
|
window: Optional['Window'], extra_data: dict[str, Any]
|
||||||
|
|
@ -110,7 +119,7 @@ def remote_control_allowed(
|
||||||
if not remote_control_passwords:
|
if not remote_control_passwords:
|
||||||
return True
|
return True
|
||||||
pw = pcmd.get('password', '')
|
pw = pcmd.get('password', '')
|
||||||
auth_items = remote_control_passwords.get(pw)
|
auth_items = _ct_password_lookup(remote_control_passwords, pw)
|
||||||
if pw == '!':
|
if pw == '!':
|
||||||
auth_items = None
|
auth_items = None
|
||||||
if auth_items is None:
|
if auth_items is None:
|
||||||
|
|
@ -185,10 +194,10 @@ def is_cmd_allowed(pcmd: dict[str, Any], window: Optional['Window'], from_socket
|
||||||
return False
|
return False
|
||||||
pa = password_authorizer(auth_items)
|
pa = password_authorizer(auth_items)
|
||||||
return pa.is_cmd_allowed(pcmd, window, from_socket, extra_data)
|
return pa.is_cmd_allowed(pcmd, window, from_socket, extra_data)
|
||||||
q = user_password_allowed.get(pw)
|
q = _ct_password_lookup(user_password_allowed, pw)
|
||||||
if q is not None:
|
if q is not None:
|
||||||
return q
|
return q
|
||||||
auth_items = get_options().remote_control_password.get(pw)
|
auth_items = _ct_password_lookup(get_options().remote_control_password, pw)
|
||||||
if auth_items is None:
|
if auth_items is None:
|
||||||
return None
|
return None
|
||||||
pa = password_authorizer(auth_items)
|
pa = password_authorizer(auth_items)
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ func ExtractAllFromTar(tr *tar.Reader, dest_path string, optss ...TarExtractOpti
|
||||||
dest_path = filepath.Clean(dest_path)
|
dest_path = filepath.Clean(dest_path)
|
||||||
|
|
||||||
mode := func(hdr int64) fs.FileMode {
|
mode := func(hdr int64) fs.FileMode {
|
||||||
return fs.FileMode(hdr) & (fs.ModePerm | fs.ModeSetgid | fs.ModeSetuid | fs.ModeSticky)
|
return fs.FileMode(hdr) & fs.ModePerm
|
||||||
}
|
}
|
||||||
|
|
||||||
set_metadata := func(chmod func(mode fs.FileMode) error, hdr_mode int64) (err error) {
|
set_metadata := func(chmod func(mode fs.FileMode) error, hdr_mode int64) (err error) {
|
||||||
|
|
@ -250,6 +250,12 @@ func ExtractAllFromTar(tr *tar.Reader, dest_path string, optss ...TarExtractOpti
|
||||||
if !filepath.IsAbs(link_target) {
|
if !filepath.IsAbs(link_target) {
|
||||||
link_target = filepath.Join(filepath.Dir(dest), link_target)
|
link_target = filepath.Join(filepath.Dir(dest), link_target)
|
||||||
}
|
}
|
||||||
|
if link_target, err = EvalSymlinksThatExist(link_target); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(link_target, needed_prefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err = os.Link(link_target, dest); err != nil {
|
if err = os.Link(link_target, dest); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue