diff --git a/tools/iso-patch/prefetch_build_deps.c b/tools/iso-patch/prefetch_build_deps.c index a5dd66e..0394c18 100644 --- a/tools/iso-patch/prefetch_build_deps.c +++ b/tools/iso-patch/prefetch_build_deps.c @@ -1,7 +1,9 @@ /* prefetch_build_deps.c - see header for design. * * Pipeline: - * 1. WinHTTP-download Packages.xz for /main/binary- + * 1. WinHTTP-download Packages.xz for the release, updates, and + * security pockets under main/binary-. Append them from + * lowest to highest priority so the latest parsed record wins. * 2. Decompress it in-process with the vendored xz-embedded decoder * (xz_decompress_file_to_file below; Packages.gz is raw-gzipped * text that tar.exe rejects, so we target the .xz instead). @@ -94,6 +96,58 @@ static BOOL u_rmdir_recursive(const wchar_t *path) return TRUE; } +/* Append src to dst, with a blank-line stanza separator. Packages + * indexes are concatenated from lowest to highest pocket priority; + * parse_packages() prepends records to each hash bucket, so lookups + * then select the record from the newest available pocket. */ +static int append_file(const wchar_t *src, const wchar_t *dst, BOOL truncate) +{ + HANDLE hIn = CreateFileW(src, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hIn == INVALID_HANDLE_VALUE) { + log_err(L"prefetch: open %s failed: %lu", src, GetLastError()); + return -1; + } + + HANDLE hOut = CreateFileW(dst, GENERIC_WRITE, 0, NULL, + truncate ? CREATE_ALWAYS : OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL); + if (hOut == INVALID_HANDLE_VALUE) { + log_err(L"prefetch: open %s failed: %lu", dst, GetLastError()); + CloseHandle(hIn); + return -1; + } + if (!truncate) { + LARGE_INTEGER zero = { 0 }; + SetFilePointerEx(hOut, zero, NULL, FILE_END); + } + + BYTE buf[65536]; + DWORD br = 0; + int rc = 0; + for (;;) { + if (!ReadFile(hIn, buf, sizeof(buf), &br, NULL)) { + log_err(L"prefetch: append read failed: %lu", GetLastError()); + rc = -1; + break; + } + if (br == 0) break; + DWORD wr = 0; + if (!WriteFile(hOut, buf, br, &wr, NULL) || wr != br) { + log_err(L"prefetch: append write failed: %lu", GetLastError()); + rc = -1; + break; + } + } + if (rc == 0) { + DWORD wr = 0; + if (!WriteFile(hOut, "\n\n", 2, &wr, NULL) || wr != 2) rc = -1; + } + CloseHandle(hOut); + CloseHandle(hIn); + return rc; +} + /* ==================================================================== * In-process XZ decompression. Reads .xz file, writes uncompressed * bytes to dst. Uses the engine/xz/ vendored xz-embedded decoder @@ -372,7 +426,7 @@ static int sha256_file(const wchar_t *path, char *hex_out /* 65 bytes */) * Memory model: read the entire Packages file into one big malloc'd * buffer; each pkg_record_t holds pointers + lengths INTO that buffer. * The pkg_table_t hashtable maps Package name (uppercased+hashed) to - * the first record with that name. + * the latest parsed record with that name. * ==================================================================== */ typedef struct pkg_record { @@ -699,24 +753,49 @@ int do_prefetch_build_deps(const wchar_t *codename, u_mkdir_p(out_dir); const wchar_t *staging = out_dir; - /* ---- 1. Download Packages.xz ---- */ - wchar_t pkgs_xz[MAX_PATH], pkgs[MAX_PATH]; - swprintf_s(pkgs_xz, MAX_PATH, L"%s\\Packages.xz", staging); - swprintf_s(pkgs, MAX_PATH, L"%s\\Packages", staging); - - wchar_t url[1024]; - swprintf_s(url, 1024, L"%s/dists/%s/main/binary-" IP_DEB_ARCH L"/Packages.xz", - mirror, codename); - log_msg(L"prefetch: GET %s", url); - if (http_download(url, pkgs_xz) != 0) { - log_err(L"prefetch: download Packages.xz failed"); - return -1; - } + /* ---- 1-2. Download + merge release pocket indexes ---- + A point-release squashfs already contains packages from -updates + and -security. Resolving dependencies only against the base + pocket can therefore select older exact-version dependencies and + make apt reject the whole offline transaction. */ + wchar_t pkgs[MAX_PATH]; + swprintf_s(pkgs, MAX_PATH, L"%s\\Packages.indexes", staging); + const wchar_t *security_mirror = mirror_arg + ? mirror + : L"http://security.ubuntu.com/ubuntu"; + for (int pocket_i = 0; pocket_i < 3; pocket_i++) { + wchar_t pocket[128]; + const wchar_t *pocket_mirror = mirror; + if (pocket_i == 0) { + wcsncpy_s(pocket, ARRAYSIZE(pocket), codename, _TRUNCATE); + } else if (pocket_i == 1) { + swprintf_s(pocket, ARRAYSIZE(pocket), L"%s-updates", codename); + } else { + swprintf_s(pocket, ARRAYSIZE(pocket), L"%s-security", codename); + pocket_mirror = security_mirror; + } - /* ---- 2. In-process xz decompression via vendored xz-embedded ---- */ - if (xz_decompress_file_to_file(pkgs_xz, pkgs) != 0) { - log_err(L"prefetch: xz decompress failed"); - return -1; + wchar_t pkgs_xz[MAX_PATH], pkgs_part[MAX_PATH], url[1024]; + swprintf_s(pkgs_xz, MAX_PATH, L"%s\\Packages.%d.xz", staging, pocket_i); + swprintf_s(pkgs_part, MAX_PATH, L"%s\\Packages.%d", staging, pocket_i); + swprintf_s(url, ARRAYSIZE(url), + L"%s/dists/%s/main/binary-" IP_DEB_ARCH L"/Packages.xz", + pocket_mirror, pocket); + log_msg(L"prefetch: GET %s", url); + if (http_download(url, pkgs_xz) != 0) { + log_err(L"prefetch: download index for %s failed", pocket); + return -1; + } + if (xz_decompress_file_to_file(pkgs_xz, pkgs_part) != 0) { + log_err(L"prefetch: decompress index for %s failed", pocket); + return -1; + } + if (append_file(pkgs_part, pkgs, pocket_i == 0) != 0) { + log_err(L"prefetch: merge index for %s failed", pocket); + return -1; + } + DeleteFileW(pkgs_xz); + DeleteFileW(pkgs_part); } /* ---- 3. Slurp Packages into memory + parse ---- */ @@ -741,14 +820,20 @@ int do_prefetch_build_deps(const wchar_t *codename, /* ---- 4. Mark seeds in_closure ---- */ const char *seeds[] = { + "build-essential", "dkms", "libasound2-dev", "libxcb1-dev", "libxcb-xfixes0-dev", "libdrm-dev", "pkg-config", "openssh-server" /* for ssh_enabled VMs; firstboot installs conditionally */ }; int closure_count = 0; + int missing_required = 0; for (size_t i = 0; i < sizeof(seeds) / sizeof(seeds[0]); i++) { pkg_record_t *r = lookup_pkg(&T, seeds[i], strlen(seeds[i])); - if (!r) { log_msg(L"prefetch: WARN seed '%hs' not in archive", seeds[i]); continue; } + if (!r) { + log_err(L"prefetch: required seed '%hs' not in archive", seeds[i]); + missing_required = 1; + continue; + } if (!r->in_closure) { r->in_closure = 1; closure_count++; } } /* Also linux-headers-. */ @@ -758,8 +843,15 @@ int do_prefetch_build_deps(const wchar_t *codename, WideCharToMultiByte(CP_UTF8, 0, kernel_ver, -1, kver_utf8, sizeof(kver_utf8), NULL, NULL); snprintf(hdr, sizeof(hdr), "linux-headers-%s", kver_utf8); pkg_record_t *r = lookup_pkg(&T, hdr, strlen(hdr)); - if (r && !r->in_closure) { r->in_closure = 1; closure_count++; } + if (!r) { + log_err(L"prefetch: required kernel headers '%hs' not in archive", hdr); + missing_required = 1; + } else if (!r->in_closure) { + r->in_closure = 1; + closure_count++; + } } + if (missing_required) return -1; /* ---- 5. BFS until stable ---- */ int total_added = closure_count; @@ -841,9 +933,9 @@ int do_prefetch_build_deps(const wchar_t *codename, write_closure_json(&T, cj, codename, kernel_ver); } - /* Clean up Packages.xz — we don't ship it (we wrote our own - synthetic Packages with closure entries only). */ - DeleteFileW(pkgs_xz); + /* Do not ship the merged upstream indexes; only the synthetic + closure index belongs in the guest staging manifest. */ + DeleteFileW(pkgs); free(T.buf); free(T.records); diff --git a/tools/iso-patch/prefetch_build_deps.h b/tools/iso-patch/prefetch_build_deps.h index aa6aa56..b87d663 100644 --- a/tools/iso-patch/prefetch_build_deps.h +++ b/tools/iso-patch/prefetch_build_deps.h @@ -4,8 +4,8 @@ * Runs as a new iso-patch.exe mode (--prefetch-build-deps) so we * don't depend on PowerShell being available on the host (corporate * AppLocker / ExecutionPolicy / etc. routinely block it). Pure - * Win32 C: WinHTTP for downloads, tar.exe shell-out for gunzip, - * BCrypt for SHA256. + * Win32 C: WinHTTP for downloads, vendored xz-embedded for index + * decompression, BCrypt for SHA256. */ #ifndef PREFETCH_BUILD_DEPS_H #define PREFETCH_BUILD_DEPS_H diff --git a/tools/iso-patch/ubuntu_vhdx.c b/tools/iso-patch/ubuntu_vhdx.c index 696650f..292be1c 100644 --- a/tools/iso-patch/ubuntu_vhdx.c +++ b/tools/iso-patch/ubuntu_vhdx.c @@ -1246,6 +1246,37 @@ static void plant_firstboot_service(ext4_writer_t *ew) "# ============================================================\n" "EXTRAS=/opt/appsandbox\n" "\n" + "# --- STEP 7.2: restore extracted filesystem capabilities + safe GTK renderer ---\n" + "# The minimal ext4 writer intentionally has no xattr support, so security.capability\n" + "# metadata from the ISO's squashfs cannot survive extraction. Ubuntu 26.04's\n" + "# snap-confine is non-setuid and receives its required permitted capabilities from\n" + "# /usr/lib/snapd/snap-confine.caps during the snapd package's postinst. We bypass\n" + "# that installer when constructing the VHDX, so reproduce its exact setcap step.\n" + "echo \"==== STEP 7.2: snap capabilities + GTK renderer policy ====\"\n" + "SNAP_CONFINE=/usr/lib/snapd/snap-confine\n" + "SNAP_CAPS=/usr/lib/snapd/snap-confine.caps\n" + "if [ -x \"$SNAP_CONFINE\" ] && [ -s \"$SNAP_CAPS\" ] && command -v setcap >/dev/null 2>&1; then\n" + " if setcap -q - \"$SNAP_CONFINE\" < \"$SNAP_CAPS\"; then\n" + " echo \"OK: restored snap-confine file capabilities\"\n" + " getcap \"$SNAP_CONFINE\" 2>&1 || true\n" + " else\n" + " echo \"FAIL: could not restore snap-confine file capabilities\"\n" + " fi\n" + "else\n" + " echo \"WARN: snap-confine capability inputs unavailable\"\n" + "fi\n" + "# Mesa dzn is an experimental Vulkan-on-D3D12 implementation. GTK4 selects Vulkan\n" + "# by default and dzn can exhaust its descriptor pool, leaving libadwaita text blank.\n" + "# Disable only GTK's Vulkan backend; its OpenGL renderer remains accelerated.\n" + "install -d -m 0755 /etc/environment.d\n" + "printf 'GDK_DISABLE=vulkan\\n' > /etc/environment.d/90-appsandbox-gtk.conf\n" + "if grep -q '^GDK_DISABLE=' /etc/environment 2>/dev/null; then\n" + " sed -i 's/^GDK_DISABLE=.*/GDK_DISABLE=vulkan/' /etc/environment\n" + "else\n" + " printf '\\nGDK_DISABLE=vulkan\\n' >> /etc/environment\n" + "fi\n" + "echo \"OK: GTK Vulkan disabled; OpenGL fallback enabled\"\n" + "\n" "# --- STEP 7.4: isolated apt sources + offline update + install all build tools ---\n" "#\n" "# Use a DEDICATED sources-parts dir so apt-get update + install only\n" @@ -1597,6 +1628,17 @@ static void plant_firstboot_service(ext4_writer_t *ew) "_check_file /etc/apt/appsandbox-sources.list.d/appsandbox-local.list\n" "_check_file /etc/apt/appsandbox-sources.list.d/appsandbox-local-extras.list\n" "_check_glob '/opt/appsandbox/local-apt-extras/*.deb'\n" + "echo \"-- desktop runtime repairs --\"\n" + "if getcap /usr/lib/snapd/snap-confine 2>/dev/null | grep -q 'cap_dac_override'; then\n" + " echo \" [OK] snap-confine capabilities\"\n" + "else\n" + " echo \" [MISSING] snap-confine capabilities\"\n" + "fi\n" + "if grep -q '^GDK_DISABLE=vulkan$' /etc/environment 2>/dev/null; then\n" + " echo \" [OK] GTK Vulkan disabled\"\n" + "else\n" + " echo \" [MISSING] GTK renderer policy\"\n" + "fi\n" "echo \"-- staged source trees (from host prefetch) --\"\n" "_check_file /opt/appsandbox/agent-src/Makefile\n" "_check_file /opt/appsandbox/asb_drm-src/dkms.conf\n" diff --git a/tools/linux/agent/appsandbox-display.c b/tools/linux/agent/appsandbox-display.c index d92de73..7211575 100644 --- a/tools/linux/agent/appsandbox-display.c +++ b/tools/linux/agent/appsandbox-display.c @@ -193,7 +193,13 @@ static int drm_acquire_fb(struct capture_ctx *c) goto out; } - drmModeFB2 *fb2 = drmModeGetFB2(c->fd, p->fb_id); + /* Preserve the framebuffer id before freeing the plane. Mutter + * rotates primary buffers continuously; reading p->fb_id after + * drmModeFreePlane() can tag the new mapping with stale heap data, + * making subsequent flips reuse the wrong (often initial blank) + * framebuffer. */ + uint32_t fb_id = p->fb_id; + drmModeFB2 *fb2 = drmModeGetFB2(c->fd, fb_id); drmModeFreePlane(p); if (!fb2) continue; @@ -223,7 +229,7 @@ static int drm_acquire_fb(struct capture_ctx *c) } drm_release_fb(c); - c->fb_id_last = p->fb_id; + c->fb_id_last = fb_id; c->width = width; c->height = height; c->stride = pitch;