From 42a2e3ab0b420c3c7c4b7c3791525c0b15352462 Mon Sep 17 00:00:00 2001 From: Kp Date: Wed, 6 Mar 2019 05:03:48 +0000 Subject: [PATCH] Avoid crash loading polymodels with invalid subcalls Truncate the model at the first error. This allows the level to load, but makes some or all of the model invisible. --- common/include/interp.h | 2 +- common/main/polyobj.h | 4 +- d1x-rebirth/main/custom.cpp | 5 ++- similar/3d/interp.cpp | 79 ++++++++++++++++++++++++++++--------- similar/main/polyobj.cpp | 16 ++++---- 5 files changed, 74 insertions(+), 32 deletions(-) diff --git a/common/include/interp.h b/common/include/interp.h index 08b29b61a..97968787d 100644 --- a/common/include/interp.h +++ b/common/include/interp.h @@ -45,7 +45,7 @@ struct glow_values_t : public array {}; void g3_draw_polygon_model(grs_bitmap *const *model_bitmaps, polygon_model_points &Interp_point_list, grs_canvas &, submodel_angles anim_angles, g3s_lrgb model_light, const glow_values_t *glow_values, const uint8_t *p); //init code for bitmap models -int16_t g3_init_polygon_model(void *model_ptr); +int16_t g3_init_polygon_model(uint8_t *model_ptr, std::size_t model_size); } #endif diff --git a/common/main/polyobj.h b/common/main/polyobj.h index e2c871887..1871409dd 100644 --- a/common/main/polyobj.h +++ b/common/main/polyobj.h @@ -61,8 +61,8 @@ constexpr std::integral_constant MAX_SUBMODELS{}; struct polymodel : prohibit_void_ptr { unsigned n_models; - int model_data_size; - std::unique_ptr model_data; + unsigned model_data_size; + std::unique_ptr model_data; array submodel_ptrs; array submodel_offsets; array submodel_norms; // norm for sep plane diff --git a/d1x-rebirth/main/custom.cpp b/d1x-rebirth/main/custom.cpp index 5b6105efe..0975f8f76 100644 --- a/d1x-rebirth/main/custom.cpp +++ b/d1x-rebirth/main/custom.cpp @@ -529,8 +529,9 @@ static void load_hxm(const d_fname &hxmname) auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models; pm = &Polygon_models[repl_num]; polymodel_read(pm, f); - pm->model_data = make_unique(pm->model_data_size); - if (PHYSFS_read(f, pm->model_data, pm->model_data_size, 1) < 1) + const auto model_data_size = pm->model_data_size; + pm->model_data = make_unique(model_data_size); + if (PHYSFS_read(f, pm->model_data, model_data_size, 1) < 1) { pm->model_data.reset(); return; diff --git a/similar/3d/interp.cpp b/similar/3d/interp.cpp index b971ea474..b50ed7ac1 100644 --- a/similar/3d/interp.cpp +++ b/similar/3d/interp.cpp @@ -18,6 +18,7 @@ #include "dxxerror.h" #include "interp.h" +#include "console.h" #include "common/3d/globvars.h" #include "polyobj.h" #include "gr.h" @@ -44,14 +45,12 @@ int g3d_interp_outline; namespace dsx { -static int16_t init_model_sub(uint8_t *p, int16_t); +static int16_t init_model_sub(uint8_t *model_sub_ptr, const uint8_t *model_base_ptr, std::size_t model_size, int16_t highest_texture_num); -#if defined(DXX_BUILD_DESCENT_I) || DXX_WORDS_BIGENDIAN static inline int16_t *wp(uint8_t *p) { return reinterpret_cast(p); } -#endif static inline const int16_t *wp(const uint8_t *p) { @@ -126,6 +125,32 @@ public: } }; +class interpreter_track_model_extent +{ +protected: + const uint8_t *const model_base; + const std::size_t model_length; +public: + constexpr interpreter_track_model_extent(const uint8_t *const b, const std::size_t l) : + model_base(b), model_length(l) + { + } + uint8_t truncate_invalid_model(const unsigned line, uint8_t *const p, const std::ptrdiff_t d, const std::size_t size) const + { + const std::ptrdiff_t offset_from_base = p - model_base; + const std::ptrdiff_t offset_of_value = offset_from_base + d; + if (offset_of_value >= model_length || offset_of_value + size >= model_length) + { + auto &opref = *wp(p); + const auto badop = opref; + opref = OP_EOF; + con_printf(CON_URGENT, "warning: %s:%u: invalid polymodel at %p with length %u; opcode %u at offset %li references invalid offset %li; replacing invalid operation with EOF", __FILE__, line, model_base, static_cast(model_length), badop, offset_from_base, offset_of_value); + return 1; + } + return 0; + } +}; + class interpreter_base { public: @@ -142,9 +167,11 @@ public: return w(p + 2); } __attribute_cold - static void op_default() + static void op_default(const unsigned op, const uint8_t *const p) { - throw std::runtime_error("invalid polygon model"); + char buf[64]; + snprintf(buf, sizeof(buf), "invalid polygon model opcode %u at %p", op, p); + throw std::runtime_error(buf); } }; @@ -415,6 +442,7 @@ public: }; class init_model_sub_state : + public interpreter_track_model_extent, public interpreter_ignore_op_defpoints, public interpreter_ignore_op_defp_start, public interpreter_ignore_op_rodbm, @@ -423,11 +451,12 @@ class init_model_sub_state : { public: int16_t highest_texture_num; - init_model_sub_state(int16_t h) : + init_model_sub_state(const uint8_t *const model_base_ptr, const std::size_t model_size, const int16_t h) : + interpreter_track_model_extent(model_base_ptr, model_size), highest_texture_num(h) { } - void op_flatpoly(uint8_t *const p, const uint_fast32_t nv) + void op_flatpoly(uint8_t *const p, const uint_fast32_t nv) const { (void)nv; Assert(nv > 2); //must have 3 or more points @@ -437,21 +466,36 @@ public: (void)p; #endif } - void op_tmappoly(const uint8_t *const p, const uint_fast32_t nv) + void op_tmappoly(uint8_t *const p, const uint_fast32_t nv) { (void)nv; Assert(nv > 2); //must have 3 or more points + if (truncate_invalid_model(__LINE__, p, 28, sizeof(uint16_t))) + return; if (w(p+28) > highest_texture_num) highest_texture_num = w(p+28); } + uint16_t init_bounded_model_sub(const unsigned line, uint8_t *const p, const std::ptrdiff_t d, const uint16_t highest_texture_num) const + { + if (truncate_invalid_model(line, p, d, sizeof(uint16_t))) + return 0; + return init_model_sub(p + d, model_base, model_length, highest_texture_num); + } void op_sortnorm(uint8_t *const p) { - auto h = init_model_sub(p+w(p+28), highest_texture_num); - highest_texture_num = init_model_sub(p+w(p+30), h); + if (truncate_invalid_model(__LINE__, p, 30, sizeof(uint16_t))) + return; + const auto n0 = w(p + 28); + const auto n1 = w(p + 30); + auto h = init_bounded_model_sub(__LINE__, p, n0, highest_texture_num); + highest_texture_num = init_bounded_model_sub(__LINE__, p, n1, h); } void op_subcall(uint8_t *const p) { - highest_texture_num = init_model_sub(p+w(p+16), highest_texture_num); + if (truncate_invalid_model(__LINE__, p, 16, sizeof(uint16_t))) + return; + const auto n0 = w(p + 16); + highest_texture_num = init_bounded_model_sub(__LINE__, p, n0, highest_texture_num); } }; @@ -509,7 +553,7 @@ static std::size_t dispatch_polymodel_op(const P p, State &state, const uint_fas return record_size; } default: - state.op_default(); + state.op_default(op, p); return 2; } } @@ -747,22 +791,21 @@ void g3_draw_morphing_model(grs_canvas &canvas, const uint8_t *const p, grs_bitm iterate_polymodel(p, state); } -static int16_t init_model_sub(uint8_t *p, int16_t highest_texture_num) +static int16_t init_model_sub(uint8_t *const model_sub_ptr, const uint8_t *const model_base_ptr, const std::size_t model_size, const int16_t highest_texture_num) { - init_model_sub_state state(highest_texture_num); + init_model_sub_state state(model_base_ptr, model_size, highest_texture_num); Assert(++nest_count < 1000); - iterate_polymodel(p, state); + iterate_polymodel(model_sub_ptr, state); return state.highest_texture_num; } //init code for bitmap models -int16_t g3_init_polygon_model(void *model_ptr) +int16_t g3_init_polygon_model(uint8_t *const model_ptr, const std::size_t model_size) { #ifndef NDEBUG nest_count = 0; #endif - - return init_model_sub(reinterpret_cast(model_ptr), -1); + return init_model_sub(model_ptr, model_ptr, model_size, -1); } } diff --git a/similar/main/polyobj.cpp b/similar/main/polyobj.cpp index 15a38d568..2d8d53d8a 100644 --- a/similar/main/polyobj.cpp +++ b/similar/main/polyobj.cpp @@ -252,8 +252,7 @@ static void align_polygon_model_data(polymodel *pm) pm->submodel_ptrs[i] += (cur_new - tmp.get()) - (cur_old - pm->model_data.get()); } pm->model_data_size += total_correction; - pm->model_data = make_unique(pm->model_data_size); - Assert(pm->model_data != NULL); + pm->model_data = make_unique(pm->model_data_size); memcpy(pm->model_data.get(), tmp.get(), pm->model_data_size); } #endif //def WORDS_NEED_ALIGNMENT @@ -398,7 +397,7 @@ static polymodel *read_model_file(polymodel *pm,const char *filename,robot_info case ID_IDTA: //Interpreter data pm->model_data_size = len; - pm->model_data = make_unique(pm->model_data_size); + pm->model_data = make_unique(pm->model_data_size); pof_cfread(pm->model_data.get(),1,len,model_buf); @@ -662,7 +661,7 @@ int load_polygon_model(const char *filename,int n_textures,int first_texture,rob polyobj_find_min_max(&model); - const auto highest_texture_num = g3_init_polygon_model(model.model_data.get()); + const auto highest_texture_num = g3_init_polygon_model(model.model_data.get(), model.model_data_size); if (highest_texture_num+1 != n_textures) Error("Model <%s> references %d textures but specifies %d.",filename,highest_texture_num+1,n_textures); @@ -751,15 +750,14 @@ void polymodel_write(PHYSFS_File *fp, const polymodel &pm) namespace dsx { void polygon_model_data_read(polymodel *pm, PHYSFS_File *fp) { - pm->model_data = make_unique(pm->model_data_size); - PHYSFS_read(fp, pm->model_data, sizeof(ubyte), pm->model_data_size); + const auto model_data_size = pm->model_data_size; + pm->model_data = make_unique(model_data_size); + PHYSFS_read(fp, pm->model_data, sizeof(uint8_t), model_data_size); #if DXX_WORDS_NEED_ALIGNMENT align_polygon_model_data(pm); #endif if (words_bigendian) swap_polygon_model_data(pm->model_data.get()); -#if defined(DXX_BUILD_DESCENT_II) - g3_init_polygon_model(pm->model_data.get()); -#endif + g3_init_polygon_model(pm->model_data.get(), model_data_size); } }