Skip to content

Invalid free in std.json.parseFree #9509

@ethernetsellout

Description

@ethernetsellout

This is what the parseFree logic currently looks like for structs & pointers:

pub fn parseFree(comptime T: type, value: T, options: ParseOptions) void {
    switch (@typeInfo(T)) {
        ...
        .Struct => |structInfo| {
            inline for (structInfo.fields) |field| {
                if (!field.is_comptime) {
                    parseFree(field.field_type, @field(value, field.name), options);
                }
            }
        },
       ...
        .Pointer => |ptrInfo| {
            const allocator = options.allocator orelse unreachable;
            switch (ptrInfo.size) {
                .One => {
                    parseFree(ptrInfo.child, value.*, options);
                    allocator.destroy(value);
                },
                .Slice => {
                    for (value) |v| {
                        parseFree(ptrInfo.child, v, options);
                    }
                    allocator.free(value);
                },
                else => unreachable,
            }
        },
        else => unreachable,
    }
}

As you can see, for structs it recursively calls itself on each field, and for pointers it deallocates them. This goes haywire when a struct field has a default value; if, for instance, a 'message' field has a default value, parseFree will attempt to free memory written in the binary:

const S = struct {message: []const u8 = "Cool default value"};

var set_message = json.TokenStream.init(
    \\{ "message": "Cool message" }
);
var leave_to_default = json.TokenStream.init(
    \\{}
);

const options = .{.allocator = std.testing.allocator};

const set_parse = try json.parse(S, &set_message, options);
const default_value_parse = try json.parse(S, &leave_to_default, options);

json.parseFree(S, set_parse, options);
json.parseFree(S, default_value_parse, options); // segfault occurs here

(https://godbolt.org/z/rYPY5or3a)

I think this issue could be fixed by adding additional checks for struct fields:

  1. Check if the field is a pointer type
  2. If so, check if the address is equal to the default value's address
  3. If it is, skip the field

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behaviorstandard libraryThis issue involves writing Zig code for the standard library.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions