Fix dialog buffer overflow handling
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user