From f7fd42e3b65b149de8229f8d0bffb9a6941856f3 Mon Sep 17 00:00:00 2001 From: jholdstock Date: Wed, 27 May 2026 09:29:35 +0800 Subject: [PATCH] jsonrpc: Avoid nil pointer dereference. This avoids a number of nil pointer dereferences by adding !nil checks before dereferencing. These checks are required for fields that are tagged with "jsonrpcdefault" because those fields will only have their default value if they have been omitted from the json string, not if they are explicitly set to "null". --- internal/rpc/jsonrpc/methods.go | 139 ++++++++++++++++++++++++++------ 1 file changed, 115 insertions(+), 24 deletions(-) diff --git a/internal/rpc/jsonrpc/methods.go b/internal/rpc/jsonrpc/methods.go index ad6fdebc8..115f8ccd9 100644 --- a/internal/rpc/jsonrpc/methods.go +++ b/internal/rpc/jsonrpc/methods.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2025 The Decred developers +// Copyright (c) 2015-2026 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -1046,7 +1046,10 @@ func (s *Server) getBalance(ctx context.Context, icmd any) (any, error) { return nil, errUnloadedWallet } - minConf := int32(*cmd.MinConf) + minConf := int32(1) + if cmd.MinConf != nil { + minConf = int32(*cmd.MinConf) + } if minConf < 0 { return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "minconf must be non-negative") } @@ -1062,7 +1065,7 @@ func (s *Server) getBalance(ctx context.Context, icmd any) (any, error) { } if accountName == "*" { - balances, err := w.AccountBalances(ctx, int32(*cmd.MinConf)) + balances, err := w.AccountBalances(ctx, minConf) if err != nil { return nil, err } @@ -1128,7 +1131,7 @@ func (s *Server) getBalance(ctx context.Context, icmd any) (any, error) { return nil, err } - bal, err := w.AccountBalance(ctx, account, int32(*cmd.MinConf)) + bal, err := w.AccountBalance(ctx, account, minConf) if err != nil { // Expect account lookup to succeed if errors.Is(err, errors.NotExist) { @@ -2392,10 +2395,15 @@ func (s *Server) getReceivedByAccount(ctx context.Context, icmd any) (any, error return 0.0, nil } + minConf := int32(1) + if cmd.MinConf != nil { + minConf = int32(*cmd.MinConf) + } + // TODO: This is more inefficient that it could be, but the entire // algorithm is already dominated by reading every transaction in the // wallet's history. - results, err := w.TotalReceivedForAccounts(ctx, int32(*cmd.MinConf)) + results, err := w.TotalReceivedForAccounts(ctx, minConf) if err != nil { return nil, err } @@ -2419,7 +2427,13 @@ func (s *Server) getReceivedByAddress(ctx context.Context, icmd any) (any, error if err != nil { return nil, err } - total, err := w.TotalReceivedForAddr(ctx, addr, int32(*cmd.MinConf)) + + minConf := int32(1) + if cmd.MinConf != nil { + minConf = int32(*cmd.MinConf) + } + + total, err := w.TotalReceivedForAddr(ctx, addr, minConf) if err != nil { if errors.Is(err, errors.NotExist) { return nil, errAddressNotInWallet @@ -2726,7 +2740,11 @@ func (s *Server) getTxOut(ctx context.Context, icmd any) (any, error) { // Attempt to read the unspent txout info from wallet. outpoint := wire.OutPoint{Hash: *txHash, Index: cmd.Vout, Tree: cmd.Tree} - utxo, err := w.UnspentOutput(ctx, outpoint, *cmd.IncludeMempool) + includeMempool := true + if cmd.IncludeMempool != nil { + includeMempool = *cmd.IncludeMempool + } + utxo, err := w.UnspentOutput(ctx, outpoint, includeMempool) if err != nil && !errors.Is(err, errors.NotExist) { return nil, err } @@ -2922,8 +2940,13 @@ func (s *Server) listAccounts(ctx context.Context, icmd any) (any, error) { return nil, errUnloadedWallet } + minConf := int32(1) + if cmd.MinConf != nil { + minConf = int32(*cmd.MinConf) + } + accountBalances := map[string]float64{} - results, err := w.AccountBalances(ctx, int32(*cmd.MinConf)) + results, err := w.AccountBalances(ctx, minConf) if err != nil { return nil, err } @@ -2978,7 +3001,12 @@ func (s *Server) listReceivedByAccount(ctx context.Context, icmd any) (any, erro return nil, errUnloadedWallet } - results, err := w.TotalReceivedForAccounts(ctx, int32(*cmd.MinConf)) + minConf := int32(1) + if cmd.MinConf != nil { + minConf = int32(*cmd.MinConf) + } + + results, err := w.TotalReceivedForAccounts(ctx, minConf) if err != nil { return nil, err } @@ -3040,7 +3068,11 @@ func (s *Server) listReceivedByAddress(ctx context.Context, icmd any) (any, erro allAddrData[address] = AddrData{} } - minConf := *cmd.MinConf + minConf := int32(1) + if cmd.MinConf != nil { + minConf = int32(*cmd.MinConf) + } + var endHeight int32 if minConf == 0 { endHeight = -1 @@ -3103,7 +3135,11 @@ func (s *Server) listSinceBlock(ctx context.Context, icmd any) (any, error) { return nil, errUnloadedWallet } - targetConf := int32(*cmd.TargetConfirmations) + targetConf := int32(1) + if cmd.TargetConfirmations != nil { + targetConf = int32(*cmd.TargetConfirmations) + } + if targetConf < 1 { return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "target_confirmations must be positive") } @@ -3169,7 +3205,16 @@ func (s *Server) listTransactions(ctx context.Context, icmd any) (any, error) { `Use "*" to reference all accounts.`) } - return w.ListTransactions(ctx, *cmd.From, *cmd.Count) + count := 10 + if cmd.Count != nil { + count = *cmd.Count + } + from := 0 + if cmd.From != nil { + from = *cmd.From + } + + return w.ListTransactions(ctx, from, count) } // listAddressTransactions handles a listaddresstransactions request by @@ -3252,7 +3297,18 @@ func (s *Server) listUnspent(ctx context.Context, icmd any) (any, error) { if cmd.Account != nil { account = *cmd.Account } - result, err := w.ListUnspent(ctx, int32(*cmd.MinConf), int32(*cmd.MaxConf), addresses, account) + + minConf := int32(1) + if cmd.MinConf != nil { + minConf = int32(*cmd.MinConf) + } + + maxConf := int32(9999999) + if cmd.MaxConf != nil { + maxConf = int32(*cmd.MaxConf) + } + + result, err := w.ListUnspent(ctx, minConf, maxConf, addresses, account) if err != nil { if errors.Is(err, errors.NotExist) { return nil, errAddressNotInWallet @@ -4171,7 +4227,12 @@ func (s *Server) rescanWallet(ctx context.Context, icmd any) (any, error) { return nil, errNoNetwork } - err := w.RescanFromHeight(ctx, n, int32(*cmd.BeginHeight)) + beginHeight := int32(0) + if cmd.BeginHeight != nil { + beginHeight = int32(*cmd.BeginHeight) + } + + err := w.RescanFromHeight(ctx, n, beginHeight) return nil, err } @@ -4342,7 +4403,12 @@ func (s *Server) ticketInfo(ctx context.Context, icmd any) (any, error) { res := make([]types.TicketInfoResult, 0) - start := wallet.NewBlockIdentifierFromHeight(*cmd.StartHeight) + startHeight := int32(0) + if cmd.StartHeight != nil { + startHeight = int32(*cmd.StartHeight) + } + + start := wallet.NewBlockIdentifierFromHeight(startHeight) end := wallet.NewBlockIdentifierFromHeight(-1) tmptx := new(wire.MsgTx) err := w.GetTickets(ctx, func(ts []*wallet.TicketSummary, h *wire.BlockHeader) (bool, error) { @@ -4441,7 +4507,10 @@ func (s *Server) sendFrom(ctx context.Context, icmd any) (any, error) { if cmd.Amount < 0 { return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "negative amount") } - minConf := int32(*cmd.MinConf) + minConf := int32(1) + if cmd.MinConf != nil { + minConf = int32(*cmd.MinConf) + } if minConf < 0 { return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "negative minconf") } @@ -4481,7 +4550,10 @@ func (s *Server) sendMany(ctx context.Context, icmd any) (any, error) { } // Check that minconf is positive. - minConf := int32(*cmd.MinConf) + minConf := int32(1) + if cmd.MinConf != nil { + minConf = int32(*cmd.MinConf) + } if minConf < 0 { return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "negative minconf") } @@ -4559,8 +4631,15 @@ func (s *Server) sendToMultiSig(ctx context.Context, icmd any) (any, error) { if err != nil { return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) } - nrequired := int8(*cmd.NRequired) - minconf := int32(*cmd.MinConf) + nrequired := int8(1) + if cmd.NRequired != nil { + nrequired = int8(*cmd.NRequired) + } + + minConf := int32(1) + if cmd.MinConf != nil { + minConf = int32(*cmd.MinConf) + } pubKeys, err := walletPubKeys(ctx, w, cmd.Pubkeys) if err != nil { @@ -4568,7 +4647,7 @@ func (s *Server) sendToMultiSig(ctx context.Context, icmd any) (any, error) { } tx, addr, script, err := - w.CreateMultisigTx(ctx, account, amount, pubKeys, nrequired, minconf) + w.CreateMultisigTx(ctx, account, amount, pubKeys, nrequired, minConf) if err != nil { return nil, err } @@ -4605,7 +4684,11 @@ func (s *Server) sendRawTransaction(ctx context.Context, icmd any) (any, error) return nil, rpcError(dcrjson.ErrRPCDeserialization, err) } - if !*cmd.AllowHighFees { + allowHighFees := false + if cmd.AllowHighFees != nil { + allowHighFees = *cmd.AllowHighFees + } + if !allowHighFees { highFees, err := txrules.TxPaysHighFees(msgtx) if err != nil { return nil, err @@ -4823,7 +4906,11 @@ func (s *Server) signRawTransaction(ctx context.Context, icmd any) (any, error) } var hashType txscript.SigHashType - switch *cmd.Flags { + flags := "ALL" + if cmd.Flags != nil { + flags = *cmd.Flags + } + switch flags { case "ALL": hashType = txscript.SigHashAll case "NONE": @@ -4906,7 +4993,7 @@ func (s *Server) signRawTransaction(ctx context.Context, icmd any) (any, error) for i, txIn := range tx.TxIn { // We don't need the first input of a stakebase tx, as it's garbage // anyway. - if i == 0 && *cmd.Flags == "ssgen" { + if i == 0 && flags == "ssgen" { continue } @@ -5053,7 +5140,11 @@ func (s *Server) signRawTransactions(ctx context.Context, icmd any) (any, error) // do that now. Otherwise, construct the slice and return it. toReturn := make([]types.SignedTransaction, len(cmd.RawTxs)) - if *cmd.Send { + send := true + if cmd.Send != nil { + send = *cmd.Send + } + if send { n, ok := s.walletLoader.NetworkBackend() if !ok { return nil, errNoNetwork