• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polserver / polserver / 35525772954

20 Sep 2026 05:24PM UTC coverage: 81.76% (+0.004%) from 81.756%
35525772954

push

github

web-flow
Refuse a gump reply whose int count wraps the length check (#975)

gumpbutton_handler summed the reply's size as a size_t and stored it in a
32-bit unsigned. Four bytes an int times a count of 0x40000000 is 2^32, so
the stored size came out as the fixed 23 bytes, passed the check against
the packet's length, and the handler then read the string count from four
gigabytes past the packet. Any player with a script gump open could crash
the server with one 23-byte reply.

Both counts are now checked by dividing the room the packet has left,
which cannot wrap, and the running offset is a size_t. The string count
had the same shape, though on a 64-bit build its product did not wrap.

Co-authored-by: murderinc <3738856+murderinc@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

4 of 4 new or added lines in 1 file covered. (100.0%)

131 existing lines in 3 files now uncovered.

60042 of 73437 relevant lines covered (81.76%)

2313353.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.71
/pol-core/bscript/bclassinstance.cpp
1
#include "bscript/bclassinstance.h"
2

3
#include "bscript/berror.h"
4
#include "bscript/bfuncref.h"
5
#include "bscript/bobject.h"
6
#include "bscript/bspecialjump.h"
7
#include "clib/clib.h"
8
#include "clib/stlutil.h"
9
#include "bscript/executor.h"
10
#include "bscript/objmembers.h"
11
#include "bscript/objmethods.h"
12

13
namespace Pol::Bscript
14
{
15
BClassInstance::BClassInstance( ref_ptr<EScriptProgram> program, int index,
578 ✔
16
                                std::weak_ptr<ValueStackCont> globals, unsigned int pid )
578 ✔
17
    : BStruct( OTClassInstance ),
18
      prog_( std::move( program ) ),
578 ✔
19
      index_( index ),
578 ✔
20
      pid_( pid ),
578 ✔
21
      globals( std::move( globals ) )
1,156 ✔
22
{
23
  passert( index_ < prog_->class_descriptors.size() );
578 ✔
24
}
578 ✔
25

26
BClassInstance::BClassInstance( const BClassInstance& B ) : BStruct( B, OTClassInstance )
×
27
{
28
  prog_ = B.prog_;
×
29
  index_ = B.index_;
×
30
  pid_ = B.pid_;
×
31
  globals = B.globals;
×
32
}
×
33

34
size_t BClassInstance::sizeEstimate() const
183 ✔
35
{
36
  return base::sizeEstimate() + Clib::memsize( constructors_called ) +
183 ✔
37
         sizeof( ref_ptr<EScriptProgram> ) + 2 * sizeof( unsigned int ) +
38
         sizeof( std::weak_ptr<ValueStackCont> );
183 ✔
39
}
40

41
ref_ptr<EScriptProgram> BClassInstance::prog() const
239 ✔
42
{
43
  return prog_;
239 ✔
44
}
45

46
unsigned BClassInstance::index() const
2,692 ✔
47
{
48
  return index_;
2,692 ✔
49
}
50

51
std::optional<unsigned> BClassInstance::findMethodIndex( const char* method_name ) const
365 ✔
52
{
53
  const auto& methods = prog_->class_descriptors[index_].methods;
365 ✔
54
  auto method_itr =
55
      std::find_if( methods.begin(), methods.end(),
365 ✔
56
                    [&]( const auto& it )
1,240 ✔
57
                    {
58
                      if ( it.first < prog_->symbols.length() )
1,240 ✔
59
                      {
60
                        return stricmp( method_name, prog_->symbols.array() + it.first ) == 0;
1,240 ✔
61
                      }
62
                      return false;
×
63
                    } );
64

65
  if ( method_itr == methods.end() )
365 ✔
66
    return {};
6 ✔
67

68
  return method_itr->second.function_reference_index;
359 ✔
69
}
70

UNCOV
71
BFunctionRef* BClassInstance::makeMethod( const char* method_name )
×
72
{
UNCOV
73
  auto funcref_index = findMethodIndex( method_name );
×
74

UNCOV
75
  if ( !funcref_index )
×
UNCOV
76
    return nullptr;
×
77

78
  return new BFunctionRef( prog_, pid_, *funcref_index, globals, ValueStackCont{} );
×
79
}
80

81
const char* BClassInstance::typetag() const
6 ✔
82
{
83
  // Return the class name as the type tag.
84
  return prog_->symbols.array() + prog_->class_descriptors[index_].name_offset;
6 ✔
85
}
86

UNCOV
87
const char* BClassInstance::typeOf() const
×
88
{
UNCOV
89
  return "Class";
×
90
}
91

UNCOV
92
u8 BClassInstance::typeOfInt() const
×
93
{
94
  return OTClassInstance;
×
95
}
96

UNCOV
97
BObjectImp* BClassInstance::copy() const
×
98
{
UNCOV
99
  passert_always_r( false, "BClassInstance::copy() should never be called" );
×
100
  return nullptr;
101
}
102

UNCOV
103
bool BClassInstance::isTrue() const
×
104
{
UNCOV
105
  return true;
×
106
}
107

108
BObjectImp* BClassInstance::call_method( const char* method_name, Executor& ex )
2,607 ✔
109
{
110
  BFunctionRef* funcr = nullptr;
2,607 ✔
111
  // owns a method function reference built for this call; the members below are borrowed instead
112
  BObjectRef method_owner;
2,607 ✔
113

114
  BObjectImp* callee{ nullptr };
2,607 ✔
115
  // Prefer members over class methods by checking contents first.
116
  auto member_itr = contents().find( method_name );
2,607 ✔
117

118
  if ( member_itr != contents().end() )
2,607 ✔
119
  {
120
    // If the member exists and is NOT a function reference, we will still try
121
    // to "call" it. This is _intentional_, and will result in a runtime
122
    // BError. This is similar to `var foo := 3; print(foo.bar());`, resulting
123
    // in a "Method 'bar' not found" error.
124
    callee = member_itr->second.get()->impptr();
9 ✔
125

126
    funcr = member_itr->second.get()->impptr_if<BFunctionRef>();
9 ✔
127
  }
128
  else
129
  {
130
    // Have we already looked up this method? The cache holds the method's index only: a cached
131
    // function reference would carry the globals and pid of whichever instance was called first,
132
    // and every later instance of the class would then run its methods against that instance's
133
    // script. The key is keyed on this instance's program, not on the executor's, because an
134
    // executor runs another program's code for the length of an external call.
135
    Executor::ClassMethodKey key{ prog_, index(), method_name };
2,598 ✔
136
    auto cache_itr = ex.class_methods.find( key );
2,598 ✔
137
    std::optional<unsigned> funcref_index;
2,598 ✔
138

139
    if ( cache_itr != ex.class_methods.end() )
2,598 ✔
140
      funcref_index = cache_itr->second;
2,233 ✔
141
    else if ( ( funcref_index = findMethodIndex( method_name ) ) )
365 ✔
142
      ex.class_methods[key] = *funcref_index;
359 ✔
143

144
    // Does the class define this method?
145
    if ( funcref_index )
2,598 ✔
146
    {
147
      // Switch the callee to the function reference: if the
148
      // funcr->validCall fails, we will go into the funcref
149
      // ins_call_method, giving the error about invalid parameter counts.
150
      method_owner.set(
2,592 ✔
151
          new BFunctionRef( prog_, pid_, *funcref_index, globals, ValueStackCont{} ) );
5,184 ✔
152
      funcr = method_owner->impptr<BFunctionRef>();
2,592 ✔
153
      callee = funcr;
2,592 ✔
154
      method_name = getObjMethod( MTH_CALL_METHOD )->code;
2,592 ✔
155
    }
156
  }
2,598 ✔
157

158
  if ( funcr != nullptr )
2,607 ✔
159
  {
160
    Instruction jmp;
2,598 ✔
161
    int id;
162

163
    // Add `this` to the front of the argument list only for class methods,
164
    // skipping eg. an instance member function reference set via
165
    // `this.foo := @(){};`.
166
    if ( funcr->class_method() )
2,598 ✔
167
    {
168
      id = MTH_CALL_METHOD;
2,592 ✔
169
      ex.fparams.insert( ex.fparams.begin(), ex.ValueStack.back() );
2,592 ✔
170
    }
171
    else
172
    {
173
      id = MTH_CALL;
6 ✔
174
    }
175

176
    if ( funcr->validCall( id, ex, &jmp ) )
2,598 ✔
177
    {
178
      BObjectRef funcobj( funcr );  // valuestack gets modified, protect BFunctionRef
2,577 ✔
179
      ex.call_function_reference( funcr, nullptr, jmp );
2,577 ✔
180
      return BSpecialUserFuncJump::get();
2,577 ✔
181
    }
2,577 ✔
182
  }
2,598 ✔
183
  if ( callee )
30 ✔
184
    return callee->call_method( method_name, ex );
24 ✔
185
  return new BError( fmt::format( "Method '{}' not found in class '{}'", method_name, typetag() ) );
12 ✔
186
}
2,607 ✔
187

188
BObjectImp* BClassInstance::call_method_id( const int id, Executor& ex, bool /*forcebuiltin*/ )
3 ✔
189
{
190
  auto method = getObjMethod( id );
3 ✔
191
  return call_method( method->code, ex );
3 ✔
192
}
193

194
BObjectRef BClassInstance::get_member_id( const int id )
130 ✔
195
{
196
  if ( id == MBR_FUNCTION )
130 ✔
197
  {
198
    const auto funcref_index =
199
        prog_->class_descriptors.at( index_ ).constructor_function_reference_index;
19 ✔
200

201
    return BObjectRef( new BFunctionRef( prog_, pid_, funcref_index, globals, ValueStackCont{} ) );
38 ✔
202
  }
203

204
  return base::get_member_id( id );
111 ✔
205
}
206

207
std::string BClassInstance::getStringRep() const
327 ✔
208
{
209
  auto class_name = prog_->symbols.array() + prog_->class_descriptors[index_].name_offset;
327 ✔
210
  return fmt::format( "<class {}>", class_name );
654 ✔
211
}
212

213
BClassInstanceRef::BClassInstanceRef( BClassInstance* inst )
3,227 ✔
214
    : BObjectImp( BObjectType::OTClassInstanceRef ), class_instance_( inst )
3,227 ✔
215
{
216
}
3,227 ✔
217

218
namespace
219
{
220
// Class instances are reference types, so one can reach itself and the walk below has no
221
// other stop condition. Unguarded it recurses until the stack overflows, from any script
222
// through SizeOf().
223
thread_local std::set<const BClassInstance*> size_estimate_visiting;
224

225
class VisitGuard
226
{
227
public:
228
  explicit VisitGuard( const BClassInstance* inst )
195 ✔
229
      : inst_( inst ), entered_( size_estimate_visiting.insert( inst ).second )
195 ✔
230
  {
231
  }
195 ✔
232
  ~VisitGuard()
195 ✔
233
  {
234
    if ( entered_ )
195 ✔
235
      size_estimate_visiting.erase( inst_ );
183 ✔
236
  }
195 ✔
237
  VisitGuard( const VisitGuard& ) = delete;
238
  VisitGuard& operator=( const VisitGuard& ) = delete;
239

240
  bool entered() const { return entered_; }
195 ✔
241

242
private:
243
  const BClassInstance* inst_;
244
  bool entered_;
245
};
246
}  // namespace
247

248
size_t BClassInstanceRef::sizeEstimate() const
195 ✔
249
{
250
  const BClassInstance* inst = class_instance_.get();
195 ✔
251
  if ( inst == nullptr )
195 ✔
UNCOV
252
    return sizeof( BClassInstanceRef );
×
253

254
  VisitGuard guard( inst );
195 ✔
255
  if ( !guard.entered() )
195 ✔
256
    return sizeof( BClassInstanceRef );  // already being measured further up: a cycle
12 ✔
257

258
  return sizeof( BClassInstanceRef ) + inst->sizeEstimate();
183 ✔
259
}
195 ✔
260

261
const char* BClassInstanceRef::typeOf() const
7 ✔
262
{
263
  return "ClassInstanceRef";
7 ✔
264
}
265

266
u8 BClassInstanceRef::typeOfInt() const
9 ✔
267
{
268
  return OTClassInstanceRef;
9 ✔
269
}
270

271
BObjectImp* BClassInstanceRef::copy() const
2,649 ✔
272
{
273
  return new BClassInstanceRef( class_instance_.get() );
2,649 ✔
274
}
275

276
bool BClassInstanceRef::isTrue() const
6 ✔
277
{
278
  return true;
6 ✔
279
}
280

281
BObjectImp* BClassInstanceRef::call_method( const char* methodname, Executor& ex )
2,604 ✔
282
{
283
  return class_instance_->call_method( methodname, ex );
2,604 ✔
284
}
285

286
BObjectImp* BClassInstanceRef::call_method_id( const int id, Executor& ex, bool forcebuiltin )
3 ✔
287
{
288
  return class_instance_->call_method_id( id, ex, forcebuiltin );
3 ✔
289
}
290

291
BObjectRef BClassInstanceRef::get_member_id( const int id )
130 ✔
292
{
293
  return class_instance_->get_member_id( id );
130 ✔
294
}
295

296
std::string BClassInstanceRef::getStringRep() const
327 ✔
297
{
298
  return class_instance_->getStringRep();
327 ✔
299
}
300

301
ContIterator* BClassInstanceRef::createIterator( BObject* pIterVal )
6 ✔
302
{
303
  return class_instance_->createIterator( pIterVal );
6 ✔
304
}
305

306
BObjectRef BClassInstanceRef::OperSubscript( const BObject& obj )
9 ✔
307
{
308
  return class_instance_->OperSubscript( obj );
9 ✔
309
}
310

311
BObjectRef BClassInstanceRef::set_member( const char* membername, BObjectImp* value, bool copy )
582 ✔
312
{
313
  return class_instance_->set_member( membername, value, copy );
582 ✔
314
}
315

316
BObjectRef BClassInstanceRef::get_member( const char* membername )
9,013 ✔
317
{
318
  return class_instance_->get_member( membername );
9,013 ✔
319
}
320

321
BObjectRef BClassInstanceRef::operDotPlus( const char* name )
6 ✔
322
{
323
  return class_instance_->operDotPlus( name );
6 ✔
324
}
325

326
BObjectRef BClassInstanceRef::operDotMinus( const char* name )
6 ✔
327
{
328
  return class_instance_->operDotMinus( name );
6 ✔
329
}
330

331
BObjectRef BClassInstanceRef::operDotQMark( const char* name )
6 ✔
332
{
333
  return class_instance_->operDotQMark( name );
6 ✔
334
}
335

336
BObjectImp* BClassInstanceRef::array_assign( BObjectImp* idx, BObjectImp* target, bool copy )
6 ✔
337
{
338
  return class_instance_->array_assign( idx, target, copy );
6 ✔
339
}
340
}  // namespace Pol::Bscript
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc