Conversation
|
|
|
||
| for _, v := range lock.Validators { | ||
| if strings.TrimPrefix(v.PublicKeyHex(), "0x") == validatorPubkeyHex { | ||
| publicKeyShare, err = v.PublicShare(int(shareIndex) - 1) |
Check failure
Code scanning / CodeQL
Incorrect conversion between integer types High test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 days ago
General approach: Avoid parsing into a type larger than the one you eventually use, or add explicit bounds ensuring the parsed value fits in the target type before casting. Here, shareIndex is only ever compared to len(lock.Operators) and then converted to int. We can safely parse it directly as a bounded signed integer (int64 with 32‑bit size limit) or a bounded unsigned integer (uint64 with 32‑bit size), and/or add a check that the value is within the range of int.
Best targeted fix without changing behaviour: keep using strconv.ParseUint but restrict the bit size to 32 (since indices and int conversions here are logically small) and ensure the value remains in the int range. Alternatively, we can switch to strconv.ParseInt with bit size 32, store in an int64, and then cast to int. That directly matches the recommendation and removes any narrowing from a larger integer type. This is slightly clearer and avoids unsigned/signed confusion.
Concretely:
- Change the declaration of
shareIndexfromuint64toint64, and parse withstrconv.ParseInt(shareIndexVar, 10, 32). - Update the bounds check to use the signed
shareIndexand ensure it’s positive and ≤int64(len(lock.Operators)). - Adjust the cast when calling
PublicShareand when indexing thepartialsmap / settingShareIdxso they all cast fromint64toint. Because we parsed with bit size 32 and ensured positivity, this is safe. - No new imports are required;
strconvis already imported andmathis not needed becauseParseInt’sbitSizeargument ensures the value fits a 32‑bit signed int.
All changes are local to HandleSubmitPartialFeeRecipient in testutil/obolapimock/feerecipient.go.
| @@ -58,14 +58,14 @@ | ||
| return | ||
| } | ||
|
|
||
| shareIndex, err := strconv.ParseUint(shareIndexVar, 10, 64) | ||
| shareIndex, err := strconv.ParseInt(shareIndexVar, 10, 32) | ||
| if err != nil { | ||
| writeErr(writer, http.StatusBadRequest, "malformed share index") | ||
| return | ||
| } | ||
|
|
||
| // check that share index is valid | ||
| if shareIndex == 0 || shareIndex > uint64(len(lock.Operators)) { | ||
| if shareIndex <= 0 || shareIndex > int64(len(lock.Operators)) { | ||
| writeErr(writer, http.StatusBadRequest, "invalid share index") | ||
| return | ||
| } |
| } | ||
| } | ||
|
|
||
| existing.partials[int(shareIndex)] = obolapi.PartialFeeRecipientResponsePartial{ |
Check failure
Code scanning / CodeQL
Incorrect conversion between integer types High test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 days ago
In general, the problem is converting a parsed uint64 into a smaller integer type without an explicit bound check against that smaller type’s limits. The cleanest fix here is to avoid the problematic conversion by using int (or another appropriately sized type) consistently instead of uint64, or alternatively by adding an explicit check that the parsed value is within the target type’s range before casting.
For this specific code, the best minimal‑change fix is:
- Parse
shareIndexdirectly as anintusingstrconv.Atoi, since the allowed range is already constrained bylen(lock.Operators), which is itself anint. This removes the need for anyuint64→intconversion. - Replace all uses of
shareIndexthat currently rely on it beinguint64(the> uint64(len(lock.Operators))comparison and the casts at lines 87 and 117–118) with theint-typed variable. - This keeps functionality identical: the semantic checks (
shareIndex == 0,shareIndex > len(lock.Operators)) and the indices passed toPublicShareand used as map keys andShareIdxremain the same, just without unsafe casting.
No new imports are needed; strconv is already imported and used.
Concretely (within testutil/obolapimock/feerecipient.go):
- Change line 61 to
shareIndex, err := strconv.Atoi(shareIndexVar). - Adjust the upper bound check on line 68 to compare
shareIndex > len(lock.Operators)instead of usinguint64. - Remove the casts
int(shareIndex)on lines 87, 117, and 118, and useshareIndexdirectly since it will now be anint.
| @@ -58,14 +58,14 @@ | ||
| return | ||
| } | ||
|
|
||
| shareIndex, err := strconv.ParseUint(shareIndexVar, 10, 64) | ||
| shareIndex, err := strconv.Atoi(shareIndexVar) | ||
| if err != nil { | ||
| writeErr(writer, http.StatusBadRequest, "malformed share index") | ||
| return | ||
| } | ||
|
|
||
| // check that share index is valid | ||
| if shareIndex == 0 || shareIndex > uint64(len(lock.Operators)) { | ||
| if shareIndex == 0 || shareIndex > len(lock.Operators) { | ||
| writeErr(writer, http.StatusBadRequest, "invalid share index") | ||
| return | ||
| } | ||
| @@ -84,7 +78,7 @@ | ||
|
|
||
| for _, v := range lock.Validators { | ||
| if strings.TrimPrefix(v.PublicKeyHex(), "0x") == validatorPubkeyHex { | ||
| publicKeyShare, err = v.PublicShare(int(shareIndex) - 1) | ||
| publicKeyShare, err = v.PublicShare(shareIndex - 1) | ||
| if err != nil { | ||
| writeErr(writer, http.StatusBadRequest, "cannot fetch public share: "+err.Error()) | ||
| return | ||
| @@ -114,8 +108,8 @@ | ||
| } | ||
| } | ||
|
|
||
| existing.partials[int(shareIndex)] = obolapi.PartialFeeRecipientResponsePartial{ | ||
| ShareIdx: int(shareIndex), | ||
| existing.partials[shareIndex] = obolapi.PartialFeeRecipientResponsePartial{ | ||
| ShareIdx: shareIndex, | ||
| Message: partialReg.Message, | ||
| Signature: partialReg.Signature[:], | ||
| } |
| } | ||
|
|
||
| existing.partials[int(shareIndex)] = obolapi.PartialFeeRecipientResponsePartial{ | ||
| ShareIdx: int(shareIndex), |
Check failure
Code scanning / CodeQL
Incorrect conversion between integer types High test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 days ago
In general, to fix this kind of issue you either: (1) parse into a type that matches the eventual use (e.g., parse with a smaller bit size if you will store into a smaller type), or (2) add explicit upper and lower bound checks before converting to a smaller integer type. The bounds should ensure that the parsed number is within the representable range of the destination type.
Here, shareIndex is already range-checked against len(lock.Operators), but CodeQL does not recognize that as a safety guarantee for the conversion to int. The simplest robust fix that does not change behavior is to avoid storing shareIndex as an int key at all. Instead, keep it as uint64 for the map key and for ShareIdx, and only convert to int in contexts where an int is required and where we have an additional, explicit bound that guarantees safety. Concretely:
- Change the
feeRecipientBlobtype sopartialsis keyed byuint64instead ofint. - When storing, use
shareIndexdirectly as the map key. - Keep
ShareIdxtyped asuint64(if that matches the underlying definition) or convert to the correct concrete type; to avoid assumptions, we will useuint64(shareIndex)so there is no narrowing conversion. - For the
v.PublicSharecall, which needs anint, we can safely convert becauseshareIndexis guaranteed to be within[1, len(lock.Operators)]. Sincelen(lock.Operators)is of typeint, the conditionshareIndex > uint64(len(lock.Operators))ensuresshareIndex-1fits inint. To make this clear and avoid repeated casts, we can introduce a localshareIndexInt := int(shareIndex)once the check has passed and use it both in thePublicSharecall and, if needed, elsewhere. However, we must keep changes minimal; therefore, we will just leave the existingint(shareIndex) - 1(which is already safe given the check) and focus on removing the unnecessaryint(shareIndex)conversions used for the map key andShareIdx.
These changes are all within testutil/obolapimock/feerecipient.go and do not require new imports or additional helper methods.
| @@ -24,7 +24,7 @@ | ||
|
|
||
| // feeRecipientBlob represents partial fee recipient registrations for a validator. | ||
| type feeRecipientBlob struct { | ||
| partials map[int]obolapi.PartialFeeRecipientResponsePartial // keyed by share index | ||
| partials map[uint64]obolapi.PartialFeeRecipientResponsePartial // keyed by share index | ||
| } | ||
|
|
||
| func (ts *testServer) HandleSubmitPartialFeeRecipient(writer http.ResponseWriter, request *http.Request) { | ||
| @@ -110,12 +110,12 @@ | ||
| existing, ok := ts.partialFeeRecipients[key] | ||
| if !ok { | ||
| existing = feeRecipientBlob{ | ||
| partials: make(map[int]obolapi.PartialFeeRecipientResponsePartial), | ||
| partials: make(map[uint64]obolapi.PartialFeeRecipientResponsePartial), | ||
| } | ||
| } | ||
|
|
||
| existing.partials[int(shareIndex)] = obolapi.PartialFeeRecipientResponsePartial{ | ||
| ShareIdx: int(shareIndex), | ||
| existing.partials[shareIndex] = obolapi.PartialFeeRecipientResponsePartial{ | ||
| ShareIdx: shareIndex, | ||
| Message: partialReg.Message, | ||
| Signature: partialReg.Signature[:], | ||
| } |



Introducing new
feerecipientcommands and Obol API model.category: feature
ticket: #4293