diff --git a/bin/mtd-rw.ko b/bin/mtd-rw.ko new file mode 100644 index 0000000..6a4b427 Binary files /dev/null and b/bin/mtd-rw.ko differ diff --git a/bin/uboot-feb2019.bin b/bin/uboot-feb2019.bin new file mode 100644 index 0000000..f363427 Binary files /dev/null and b/bin/uboot-feb2019.bin differ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..bfc96a0 --- /dev/null +++ b/build.zig @@ -0,0 +1,21 @@ +const Builder = @import("std").build.Builder; +const builtin = @import("builtin"); + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + const exe = b.addExecutable("update-em-bootloader", "src/main.zig"); + exe.setBuildMode(builtin.Mode.ReleaseSmall); + exe.strip = true; + + exe.setTarget(builtin.Arch.mipsel, builtin.Os.linux, builtin.Abi.musl); + + exe.addIncludeDir("/usr/include"); + exe.linkSystemLibrary("c"); + exe.install(); + + const run_cmd = exe.run(); + run_cmd.step.dependOn(b.getInstallStep()); + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..914db96 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,43 @@ +const std = @import("std"); +const math = std.math; +const mtd_user = @cImport(@cInclude("mtd/mtd-user.h")); + +const uboot_bin = @embedFile("../bin/uboot-feb2019.bin"); +const mtd_rw = @import("mtd-rw.zig"); +const mtd = @import("mtd.zig"); + +pub fn main() !u8 { + try mtd_rw.load(); + defer mtd_rw.unload(); + + // Read and hash mtd0 + const mtd0_size = try mtd.mtd_info_get_usize("size"); + + if (uboot_bin.len > mtd0_size) { + std.debug.warn("Error: mtd0 isn\'t large enough to hold the new image ({} > {})\n", + .{uboot_bin.len, mtd0_size}); + return 1; + } + + const no_write_needed = try mtd.verify("/dev/mtd0", uboot_bin); + if (no_write_needed) { + std.debug.warn("U-Boot is up to date. Exiting.\n", .{}); + return 0; + } + + // Erase mtd0 + try mtd.erase_device("/dev/mtd0"); + + // Write mtd0 + std.debug.warn("Writing payload\n", .{}); + try mtd.write_device("/dev/mtd0", uboot_bin); + + // Verify mtd0 + const writing_verified = try mtd.verify("/dev/mtd0", uboot_bin); + if (!writing_verified) { + std.debug.warn("Verify Failed!\n", .{}); + return 2; + } + std.debug.warn("Success\n", .{}); + return 0; +} diff --git a/src/mtd-rw.zig b/src/mtd-rw.zig new file mode 100644 index 0000000..5e567a5 --- /dev/null +++ b/src/mtd-rw.zig @@ -0,0 +1,69 @@ +const std = @import("std"); +const os = std.os; + +const mtd_rw_ko = @embedFile("../bin/mtd-rw.ko"); + +const InitModuleError = error { + ModuleSignatureMisformatted, + TimeoutResolvingSymbol, + BadAddress, + ModuleSigInvalid, + OutOfMemory, + PermissionDenied, + AlreadyLoaded, + BadParams, + InvalidModule, + +}; + +const DeleteModuleError = error { + ModuleNotLive, + BadAddress, + ModuleNotFound, + PermissionDenied, + ModuleInUse, +}; + +pub fn load() !void { + try insmod(mtd_rw_ko, "i_want_a_brick=1"); +} + +pub fn unload() void { + rmmod("mtd_rw", os.O_NONBLOCK) catch |err| { + std.debug.warn("Failed to unload module: {}\n", .{err}); + }; +} + +pub fn insmod(buf: []const u8, args: [*:0]const u8) !void { + const errno = os.linux.getErrno( + os.linux.syscall3( + os.linux.SYS_init_module, @ptrToInt(&buf[0]), buf.len, @ptrToInt(args))); + switch (errno) { + 0 => return, + os.EEXIST => return, // It's not a failure if we have what we need + os.EBADMSG => return InitModuleError.ModuleSignatureMisformatted, + os.EBUSY => return InitModuleError.TimeoutResolvingSymbol, + os.EFAULT => return InitModuleError.BadAddress, + // os.ENOKEY => return InitModuleError.ModuleSigInvalid, + os.ENOMEM => return InitModuleError.OutOfMemory, + os.EPERM => return InitModuleError.PermissionDenied, + os.EINVAL => return InitModuleError.BadParams, + os.ENOEXEC => return InitModuleError.InvalidModule, + else => |err| return os.unexpectedErrno(err), + } +} + +pub fn rmmod(mod_name: [*:0]const u8, flags: u32) !void { + const errno = os.linux.getErrno( + std.os.linux.syscall2( + std.os.linux.SYS_delete_module, @ptrToInt(mod_name), flags)); + switch (errno) { + 0 => return, + os.EBUSY => return DeleteModuleError.ModuleNotLive, + os.EFAULT => return DeleteModuleError.BadAddress, + os.ENOENT => return DeleteModuleError.ModuleNotFound, + os.EPERM => return DeleteModuleError.PermissionDenied, + os.EWOULDBLOCK => return DeleteModuleError.ModuleInUse, + else => |err| return os.unexpectedErrno(err), + } +} diff --git a/src/mtd.zig b/src/mtd.zig new file mode 100644 index 0000000..95665c5 --- /dev/null +++ b/src/mtd.zig @@ -0,0 +1,105 @@ +const std = @import("std"); +const os = std.os; + +const mtd_user = @cImport(@cInclude("mtd/mtd-user.h")); + +const Allocator = std.heap.c_allocator; + +const MEMUNLOCK = 2148027654; +const MEMERASE = 2148027650; + +pub fn mtd_info_get(name: []const u8, out: []u8) !void { + var path_buf: [64]u8 = undefined; + const path = try std.fmt.bufPrint(path_buf[0..], "{}/{}", .{"/sys/class/mtd/mtd0/", name}); + + var f = try std.fs.openFileAbsolute(path, .{.read = true}); + defer f.close(); + const num_read = try f.read(out); + out = out[0..num_read-1]; +} + +pub fn mtd_info_get_usize(name: []const u8) !usize { + var buf: [64]u8 = undefined; + var buf_sl = buf[0..]; + try mtd_info_get(name, buf_sl); + return try std.fmt.parseUnsigned(usize, buf_sl, 10); +} + +const IoctlError = error { + BadFd, + SignalCaught, + BadIoctl, + IOError, + NotSupported, + NotSupportedSub, + Failed, +}; + +fn ioctl3(fildes: os.fd_t, request: usize, arg: usize) !void { + const errno = os.linux.getErrno( + os.linux.syscall3(std.os.SYS_ioctl, @intCast(usize, fildes), request, arg)); + switch (errno) { + 0 => return, + os.EBADF => return IoctlError.BadFd, + os.EINTR => return IoctlError.SignalCaught, + os.EINVAL => return IoctlError.BadIoctl, + os.EIO => return IoctlError.IOError, + os.ENOTTY => return IoctlError.NotSupportedSub, + os.ENXIO => return IoctlError.Failed, + os.ENODEV => return IoctlError.NotSupported, + else => |err| return os.unexpectedErrno(err), + } +} + +const MtdError = error { + TruncatedWrite, + TruncatedRead, +}; + +pub fn write_device(dev: []const u8, buf: []const u8) !void { + var f = try std.fs.openFileAbsolute(dev, .{.write = true}); + defer f.close(); + try f.write(buf); +} + +pub fn erase_device(dev: []const u8) !void { + const erasesize = try mtd_info_get_usize("erasesize"); + const size = try mtd_info_get_usize("size"); + var erased: usize = 0; + const fd = try os.open(dev, os.O_RDWR, 0); + defer os.close(fd); + while (erased < size) { + try erase_page(fd, erased, erasesize); + erased += erasesize; + } +} + +pub fn erase_page(fd: os.fd_t, start: usize, len: usize) !void { + const end = start + len - 1; + std.debug.warn("Erasing {} -> {}\n", .{start, end}); + + const ei = mtd_user.erase_info_t{ .length = len, .start = start }; + try ioctl3(fd, MEMERASE, @ptrToInt(&ei)); +} + +pub fn verify(dev: []const u8, buf: []const u8) !bool { + const size = try mtd_info_get_usize("size"); + var current = try Allocator.alloc(u8, size); + + var f = try std.fs.openFileAbsolute(dev, .{.read = true}); + defer f.close(); + + const num_read = try f.read(current); + if (num_read != size) { + std.debug.warn("Incomplete read of {}. {}/{} bytes read", .{dev, num_read, size}); + return MtdError.TruncatedRead; + } + + var dev_digest: [std.crypto.Md5.digest_length]u8 = undefined; + std.crypto.Md5.hash(current, dev_digest[0..]); + + var buf_digest: [std.crypto.Md5.digest_length]u8 = undefined; + std.crypto.Md5.hash(buf, buf_digest[0..]); + + return std.mem.eql(u8, dev_digest[0..], buf_digest[0..]); +}