Skip to content

Commit d9a1585

Browse files
committed
Patch Lua 5.1 using the build system
Adds a super simple executable to apply patches during build-time. It might fail for more complex patches but it works well enough for the patch needed for Lua 5.1. Closes #109
1 parent 862e597 commit d9a1585

File tree

3 files changed

+173
-1
lines changed

3 files changed

+173
-1
lines changed

build/lua-5.1.patch

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Patch for CVE-2014-5461
2+
# See https://nvd.nist.gov/vuln/detail/CVE-2014-5461
3+
--- a/src/ldo.c
4+
+++ b/src/ldo.c
5+
@@ -274,7 +274,7 @@ int luaD_precall (lua_State *L, StkId func, int nresults) {
6+
CallInfo *ci;
7+
StkId st, base;
8+
Proto *p = cl->p;
9+
- luaD_checkstack(L, p->maxstacksize);
10+
+ luaD_checkstack(L, p->maxstacksize + p->numparams);
11+
func = restorestack(L, funcr);
12+
if (!p->is_vararg) { /* no varargs? */
13+
base = func + 1;

build/lua.zig

+32-1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.
7171
.flags = &flags,
7272
});
7373

74+
// Patch ldo.c for Lua 5.1
75+
if (lang == .lua51) {
76+
const patched = patchFile(b, target, lib, upstream.path("src/ldo.c"), b.path("build/lua-5.1.patch"));
77+
lib.addCSourceFile(.{ .file = patched, .flags = &flags });
78+
}
79+
7480
lib.linkLibC();
7581

7682
lib.installHeader(upstream.path("src/lua.h"), "lua.h");
@@ -81,11 +87,33 @@ pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.
8187
return lib;
8288
}
8389

90+
fn patchFile(
91+
b: *Build,
92+
target: Build.ResolvedTarget,
93+
lib: *Step.Compile,
94+
file: Build.LazyPath,
95+
patch_file: Build.LazyPath,
96+
) Build.LazyPath {
97+
const patch = b.addExecutable(.{
98+
.name = "patch",
99+
.root_source_file = b.path("build/patch.zig"),
100+
.target = target,
101+
});
102+
103+
const patch_run = b.addRunArtifact(patch);
104+
patch_run.addFileArg(file);
105+
patch_run.addFileArg(patch_file);
106+
const out = patch_run.addOutputFileArg("ldo.c");
107+
108+
lib.step.dependOn(&patch_run.step);
109+
110+
return out;
111+
}
112+
84113
const lua_base_source_files = [_][]const u8{
85114
"src/lapi.c",
86115
"src/lcode.c",
87116
"src/ldebug.c",
88-
"src/ldo.c",
89117
"src/ldump.c",
90118
"src/lfunc.c",
91119
"src/lgc.c",
@@ -114,19 +142,22 @@ const lua_base_source_files = [_][]const u8{
114142
};
115143

116144
const lua_52_source_files = lua_base_source_files ++ [_][]const u8{
145+
"src/ldo.c",
117146
"src/lctype.c",
118147
"src/lbitlib.c",
119148
"src/lcorolib.c",
120149
};
121150

122151
const lua_53_source_files = lua_base_source_files ++ [_][]const u8{
152+
"src/ldo.c",
123153
"src/lctype.c",
124154
"src/lbitlib.c",
125155
"src/lcorolib.c",
126156
"src/lutf8lib.c",
127157
};
128158

129159
const lua_54_source_files = lua_base_source_files ++ [_][]const u8{
160+
"src/ldo.c",
130161
"src/lctype.c",
131162
"src/lcorolib.c",
132163
"src/lutf8lib.c",

build/patch.zig

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//! A simple script to apply a patch to a file
2+
//! Does minimal validation and is just enough for patching Lua 5.1
3+
4+
pub fn main() !void {
5+
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
6+
defer arena.deinit();
7+
8+
const allocator = arena.allocator();
9+
10+
const args = try std.process.argsAlloc(allocator);
11+
if (args.len != 4) @panic("Wrong number of arguments");
12+
13+
const file_path = args[1];
14+
const patch_file_path = args[2];
15+
const output_path = args[3];
16+
17+
const patch_file = try std.fs.openFileAbsolute(patch_file_path, .{ .mode = .read_only });
18+
defer patch_file.close();
19+
const chunk_details = Chunk.next(allocator, patch_file) orelse @panic("No chunk data found");
20+
21+
const file = try std.fs.openFileAbsolute(file_path, .{ .mode = .read_only });
22+
defer file.close();
23+
24+
const output = try std.fs.createFileAbsolute(output_path, .{});
25+
defer output.close();
26+
27+
var state: State = .copy;
28+
29+
var line_number: usize = 1;
30+
while (true) : (line_number += 1) {
31+
if (line_number == chunk_details.src) state = .chunk;
32+
33+
switch (state) {
34+
.copy => {
35+
const line = getLine(allocator, file) orelse return;
36+
_ = try output.write(line);
37+
_ = try output.write("\n");
38+
},
39+
.chunk => {
40+
const chunk = chunk_details.lines[line_number - chunk_details.src];
41+
switch (chunk.action) {
42+
.remove => {
43+
const line = getLine(allocator, file) orelse return;
44+
if (!std.mem.eql(u8, chunk.buf, line)) @panic("Failed to apply patch");
45+
},
46+
.keep => {
47+
const line = getLine(allocator, file) orelse return;
48+
if (!std.mem.eql(u8, chunk.buf, line)) @panic("Failed to apply patch");
49+
_ = try output.write(line);
50+
_ = try output.write("\n");
51+
},
52+
.add => {
53+
_ = try output.write(chunk.buf);
54+
_ = try output.write("\n");
55+
},
56+
}
57+
58+
if (line_number - chunk_details.src == chunk_details.lines.len - 1) state = .copy;
59+
},
60+
}
61+
}
62+
}
63+
64+
fn getLine(allocator: Allocator, file: File) ?[]u8 {
65+
return file.reader().readUntilDelimiterAlloc(allocator, '\n', std.math.maxInt(usize)) catch |err| switch (err) {
66+
error.EndOfStream => return null,
67+
else => @panic("Error"),
68+
};
69+
}
70+
71+
const State = enum { copy, chunk };
72+
73+
const Chunk = struct {
74+
lines: []Line,
75+
src: usize,
76+
dst: usize,
77+
78+
const Line = struct {
79+
const Action = enum { remove, keep, add };
80+
81+
action: Action,
82+
buf: []const u8,
83+
};
84+
85+
fn next(allocator: Allocator, file: File) ?Chunk {
86+
while (true) {
87+
const line = getLine(allocator, file) orelse return null;
88+
if (std.mem.startsWith(u8, line, "@@")) {
89+
const end = std.mem.indexOfPosLinear(u8, line, 3, "@@").?;
90+
const details = line[4 .. end - 2];
91+
92+
const space_index = std.mem.indexOfScalar(u8, details, ' ').?;
93+
const src = getLineNumber(details[0..space_index]);
94+
const dst = getLineNumber(details[space_index + 1 ..]);
95+
96+
var lines: std.ArrayListUnmanaged(Line) = .empty;
97+
while (true) {
98+
const diff_line = getLine(allocator, file) orelse break;
99+
if (std.mem.startsWith(u8, diff_line, "@@")) break;
100+
101+
const action: Line.Action = switch (diff_line[0]) {
102+
'-' => .remove,
103+
' ' => .keep,
104+
'+' => .add,
105+
else => @panic("Bad patch file"),
106+
};
107+
108+
lines.append(allocator, .{ .action = action, .buf = diff_line[1..] }) catch unreachable;
109+
}
110+
111+
return .{
112+
.lines = lines.toOwnedSlice(allocator) catch unreachable,
113+
.src = src,
114+
.dst = dst,
115+
};
116+
}
117+
}
118+
}
119+
120+
fn getLineNumber(buf: []const u8) usize {
121+
const comma = std.mem.indexOfScalar(u8, buf, ',').?;
122+
return std.fmt.parseInt(usize, buf[0..comma], 10) catch unreachable;
123+
}
124+
};
125+
126+
const std = @import("std");
127+
const Allocator = std.mem.Allocator;
128+
const File = std.fs.File;

0 commit comments

Comments
 (0)