Fix dialog buffer overflow handling

This commit is contained in:
Chris Tate
2026-06-27 00:57:49 -05:00
parent f5d49207cd
commit 69d8f29ccc
8 changed files with 71 additions and 15 deletions
+17 -4
View File
@@ -4,6 +4,7 @@
#include <webkit/webkit.h>
#include <glib/gstdio.h>
#include <dlfcn.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
@@ -20,6 +21,10 @@
#define ZERO_NATIVE_SHORTCUT_MODIFIER_OPTION (1u << 3)
#define ZERO_NATIVE_SHORTCUT_MODIFIER_SHIFT (1u << 4)
static size_t zero_native_overflow_size(size_t buffer_len) {
return buffer_len == SIZE_MAX ? SIZE_MAX : buffer_len + 1;
}
#define ZERO_NATIVE_GTK_VIEW_WEBVIEW 0
#define ZERO_NATIVE_GTK_VIEW_TOOLBAR 1
#define ZERO_NATIVE_GTK_VIEW_TITLEBAR_ACCESSORY 2
@@ -2809,6 +2814,7 @@ zero_native_gtk_open_dialog_result_t zero_native_gtk_show_open_dialog(zero_nativ
g_main_loop_unref(state.loop);
if (state.files) {
size_t offset = 0;
int overflow = 0;
guint count = g_list_model_get_n_items(state.files);
for (guint i = 0; i < count; i++) {
GFile *file = G_FILE(g_list_model_get_item(state.files, i));
@@ -2816,17 +2822,20 @@ zero_native_gtk_open_dialog_result_t zero_native_gtk_show_open_dialog(zero_nativ
if (path) {
size_t len = strlen(path);
size_t needed = len + (result.count > 0 ? 1 : 0);
if (offset + needed <= buffer_len) {
if (needed <= buffer_len - offset) {
if (result.count > 0) buffer[offset++] = '\n';
memcpy(buffer + offset, path, len);
offset += len;
result.count++;
} else {
overflow = 1;
}
g_free(path);
}
g_object_unref(file);
if (overflow) break;
}
result.bytes_written = offset;
result.bytes_written = overflow ? zero_native_overflow_size(buffer_len) : offset;
g_object_unref(state.files);
}
if (title) free(title);
@@ -2856,8 +2865,12 @@ size_t zero_native_gtk_show_save_dialog(zero_native_gtk_host_t *host, const zero
char *path = g_file_get_path(state.file);
if (path) {
size_t len = strlen(path);
written = len < buffer_len ? len : buffer_len;
memcpy(buffer, path, written);
if (len > buffer_len) {
written = zero_native_overflow_size(buffer_len);
} else {
written = len;
memcpy(buffer, path, written);
}
g_free(path);
}
g_object_unref(state.file);
+2
View File
@@ -643,6 +643,7 @@ fn showOpenDialog(context: ?*anyopaque, options: platform_mod.OpenDialogOptions,
.allow_multiple = if (options.allow_multiple) 1 else 0,
};
const result = zero_native_gtk_show_open_dialog(self.host, &opts, buffer.ptr, buffer.len);
if (result.bytes_written > buffer.len) return error.NoSpaceLeft;
return .{ .count = result.count, .paths = buffer[0..result.bytes_written] };
}
@@ -661,6 +662,7 @@ fn showSaveDialog(context: ?*anyopaque, options: platform_mod.SaveDialogOptions,
.extensions_len = ext_str.len,
};
const written = zero_native_gtk_show_save_dialog(self.host, &opts, buffer.ptr, buffer.len);
if (written > buffer.len) return error.NoSpaceLeft;
if (written == 0) return null;
return buffer[0..written];
}
+13 -3
View File
@@ -5,6 +5,7 @@
#import <CoreFoundation/CoreFoundation.h>
#import <Security/Security.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#include <stdint.h>
#include <string.h>
@class ZeroNativeAppKitHost;
@@ -32,6 +33,10 @@ static BOOL ZeroNativeShortcutModifiersMatch(uint32_t shortcutModifiers, NSEvent
static NSEventModifierFlags ZeroNativeMenuModifierFlags(uint32_t modifiers);
static NSAccessibilityRole ZeroNativeAccessibilityRoleForNativeViewKind(NSInteger kind);
static size_t ZeroNativeOverflowSize(size_t buffer_len) {
return buffer_len == SIZE_MAX ? SIZE_MAX : buffer_len + 1;
}
static NSString *ZeroNativeStringFromBytes(const char *bytes, size_t len) {
if (!bytes || len == 0) return nil;
return [[NSString alloc] initWithBytes:bytes length:len encoding:NSUTF8StringEncoding];
@@ -2497,18 +2502,22 @@ zero_native_appkit_open_dialog_result_t zero_native_appkit_show_open_dialog(zero
if ([panel runModal] != NSModalResponseOK) return result;
size_t offset = 0;
BOOL overflow = NO;
for (NSURL *url in panel.URLs) {
NSString *path = url.path;
NSData *data = [path dataUsingEncoding:NSUTF8StringEncoding];
if (!data) continue;
size_t needed = data.length + (result.count > 0 ? 1 : 0);
if (offset + needed > buffer_len) break;
if (needed > buffer_len - offset) {
overflow = YES;
break;
}
if (result.count > 0) { buffer[offset] = '\n'; offset++; }
memcpy(buffer + offset, data.bytes, data.length);
offset += data.length;
result.count++;
}
result.bytes_written = offset;
result.bytes_written = overflow ? ZeroNativeOverflowSize(buffer_len) : offset;
}
return result;
}
@@ -2534,7 +2543,8 @@ size_t zero_native_appkit_show_save_dialog(zero_native_appkit_host_t *host, cons
NSString *path = panel.URL.path;
NSData *data = [path dataUsingEncoding:NSUTF8StringEncoding];
if (!data) return 0;
size_t count = MIN(buffer_len, data.length);
size_t count = data.length;
if (count > buffer_len) return ZeroNativeOverflowSize(buffer_len);
memcpy(buffer, data.bytes, count);
return count;
}
+13 -3
View File
@@ -5,6 +5,7 @@
#import <Security/Security.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#include <crt_externs.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -2073,6 +2074,10 @@ static NSArray<NSString *> *ZeroNativeParseExtensions(const char *extensions, si
return result.count > 0 ? result : nil;
}
static size_t ZeroNativeOverflowSize(size_t buffer_len) {
return buffer_len == SIZE_MAX ? SIZE_MAX : buffer_len + 1;
}
static void ZeroNativeConfigurePanelExtensions(NSSavePanel *panel, NSArray<NSString *> *extensions) {
if (!extensions || extensions.count == 0) return;
if (@available(macOS 11.0, *)) {
@@ -2105,18 +2110,22 @@ zero_native_appkit_open_dialog_result_t zero_native_appkit_show_open_dialog(zero
if ([panel runModal] != NSModalResponseOK) return result;
size_t offset = 0;
BOOL overflow = NO;
for (NSURL *url in panel.URLs) {
NSString *path = url.path;
NSData *data = [path dataUsingEncoding:NSUTF8StringEncoding];
if (!data) continue;
size_t needed = data.length + (result.count > 0 ? 1 : 0);
if (offset + needed > buffer_len) break;
if (needed > buffer_len - offset) {
overflow = YES;
break;
}
if (result.count > 0) { buffer[offset] = '\n'; offset++; }
memcpy(buffer + offset, data.bytes, data.length);
offset += data.length;
result.count++;
}
result.bytes_written = offset;
result.bytes_written = overflow ? ZeroNativeOverflowSize(buffer_len) : offset;
}
return result;
}
@@ -2142,7 +2151,8 @@ size_t zero_native_appkit_show_save_dialog(zero_native_appkit_host_t *host, cons
NSString *path = panel.URL.path;
NSData *data = [path dataUsingEncoding:NSUTF8StringEncoding];
if (!data) return 0;
size_t count = MIN(buffer_len, data.length);
size_t count = data.length;
if (count > buffer_len) return ZeroNativeOverflowSize(buffer_len);
memcpy(buffer, data.bytes, count);
return count;
}
+2
View File
@@ -889,6 +889,7 @@ fn showOpenDialog(context: ?*anyopaque, options: platform_mod.OpenDialogOptions,
.allow_multiple = if (options.allow_multiple) 1 else 0,
};
const result = zero_native_appkit_show_open_dialog(self.host, &opts, buffer.ptr, buffer.len);
if (result.bytes_written > buffer.len) return error.NoSpaceLeft;
return .{
.count = result.count,
.paths = buffer[0..result.bytes_written],
@@ -910,6 +911,7 @@ fn showSaveDialog(context: ?*anyopaque, options: platform_mod.SaveDialogOptions,
.extensions_len = ext_str.len,
};
const written = zero_native_appkit_show_save_dialog(self.host, &opts, buffer.ptr, buffer.len);
if (written > buffer.len) return error.NoSpaceLeft;
if (written == 0) return null;
return buffer[0..written];
}
+2
View File
@@ -628,6 +628,7 @@ fn showOpenDialog(context: ?*anyopaque, options: platform_mod.OpenDialogOptions,
.allow_multiple = if (options.allow_multiple) 1 else 0,
};
const result = zero_native_windows_show_open_dialog(self.host, &opts, buffer.ptr, buffer.len);
if (result.bytes_written > buffer.len) return error.NoSpaceLeft;
return .{ .count = result.count, .paths = buffer[0..result.bytes_written] };
}
@@ -647,6 +648,7 @@ fn showSaveDialog(context: ?*anyopaque, options: platform_mod.SaveDialogOptions,
.extensions_len = ext_str.len,
};
const written = zero_native_windows_show_save_dialog(self.host, &opts, buffer.ptr, buffer.len);
if (written > buffer.len) return error.NoSpaceLeft;
if (written == 0) return null;
return buffer[0..written];
}
+18 -5
View File
@@ -415,6 +415,10 @@ static void finishCom(bool uninitialize) {
if (uninitialize) CoUninitialize();
}
static size_t overflowSize(size_t buffer_len) {
return buffer_len == SIZE_MAX ? SIZE_MAX : buffer_len + 1;
}
static void setDialogTitle(IFileDialog *dialog, const char *title, size_t title_len) {
if (!dialog || !title || title_len == 0) return;
std::wstring title_wide = widen(slice(title, title_len));
@@ -470,9 +474,12 @@ static void setDialogFilters(IFileDialog *dialog, const char *extensions, size_t
static bool appendPathToBuffer(char *buffer, size_t buffer_len, size_t *offset, size_t *count, const std::wstring &path_wide) {
std::string path = narrow(path_wide);
if (path.empty()) return false;
if (path.empty()) return true;
size_t needed = path.size() + (*count > 0 ? 1 : 0);
if (*offset + needed > buffer_len) return false;
if (needed > buffer_len - *offset) {
*offset = overflowSize(buffer_len);
return false;
}
if (*count > 0) buffer[(*offset)++] = '\n';
memcpy(buffer + *offset, path.data(), path.size());
*offset += path.size();
@@ -2484,12 +2491,14 @@ WindowsOpenDialogResult zero_native_windows_show_open_dialog(Host *host, const W
for (DWORD index = 0; index < count; ++index) {
IShellItem *item = nullptr;
if (SUCCEEDED(items->GetItemAt(index, &item)) && item) {
bool overflow = false;
PWSTR path = nullptr;
if (SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &path)) && path) {
appendPathToBuffer(buffer, buffer_len, &offset, &written_count, std::wstring(path));
overflow = !appendPathToBuffer(buffer, buffer_len, &offset, &written_count, std::wstring(path));
CoTaskMemFree(path);
}
item->Release();
if (overflow) break;
}
}
result.count = written_count;
@@ -2530,8 +2539,12 @@ size_t zero_native_windows_show_save_dialog(Host *host, const WindowsSaveDialogO
PWSTR path = nullptr;
if (SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &path)) && path) {
std::string utf8_path = narrow(std::wstring(path));
written = utf8_path.size() < buffer_len ? utf8_path.size() : buffer_len;
if (written > 0) memcpy(buffer, utf8_path.data(), written);
if (utf8_path.size() > buffer_len) {
written = overflowSize(buffer_len);
} else {
written = utf8_path.size();
if (written > 0) memcpy(buffer, utf8_path.data(), written);
}
CoTaskMemFree(path);
}
item->Release();
+4
View File
@@ -6934,6 +6934,8 @@ test "runtime validates native OS actions before platform dispatch" {
var dialog_paths: [platform.max_dialog_paths_bytes]u8 = undefined;
try std.testing.expectError(error.InvalidDialogOptions, harness.runtime.showOpenDialog(.{}, dialog_paths[0..0]));
var small_dialog_paths: [4]u8 = undefined;
try std.testing.expectError(error.NoSpaceLeft, harness.runtime.showOpenDialog(.{}, &small_dialog_paths));
const long_dialog_title = [_]u8{'x'} ** (platform.max_dialog_title_bytes + 1);
try std.testing.expectError(error.DialogFieldTooLarge, harness.runtime.showOpenDialog(.{ .title = &long_dialog_title }, &dialog_paths));
const open_result = try harness.runtime.showOpenDialog(.{ .title = "Open" }, &dialog_paths);
@@ -6941,6 +6943,8 @@ test "runtime validates native OS actions before platform dispatch" {
try std.testing.expectEqualStrings("/tmp/zero-native-open.txt", open_result.paths);
var save_path: [platform.max_dialog_path_bytes]u8 = undefined;
var small_save_path: [4]u8 = undefined;
try std.testing.expectError(error.NoSpaceLeft, harness.runtime.showSaveDialog(.{ .default_name = "report.txt" }, &small_save_path));
const saved = (try harness.runtime.showSaveDialog(.{ .default_name = "report.txt" }, &save_path)).?;
try std.testing.expectEqualStrings("report.txt", saved);