tx: max_tx_file_size, additional checks during parsing
Since the tx file is potentially untrusted data, be strict about what we allow: - check data size against max_tx_file_size (defaults to 100000 bytes) - check for ascii encoding - check line lengths - reject extraneous keys in inputs and outputs (done by MMGenListItem) max_tx_file_size can be overridden in config file
This commit is contained in:
parent
e2d51469e6
commit
cf20311af5
3 changed files with 24 additions and 12 deletions
|
|
@ -59,6 +59,9 @@
|
|||
# multiplied by this value:
|
||||
# tx_fee_adj 1.0
|
||||
|
||||
# Set the maximum transaction file size:
|
||||
# max_tx_file_size 100000
|
||||
|
||||
#####################################################################
|
||||
# The following options are probably of interest only to developers #
|
||||
#####################################################################
|
||||
|
|
|
|||
|
|
@ -130,7 +130,8 @@ class g(object):
|
|||
'color','debug','hash_preset','http_timeout','no_license','rpc_host','rpc_port',
|
||||
'quiet','tx_fee_adj','usr_randchars','testnet','rpc_user','rpc_password',
|
||||
'daemon_data_dir','force_256_color','regtest',
|
||||
'btc_max_tx_fee','ltc_max_tx_fee','bch_max_tx_fee'
|
||||
'btc_max_tx_fee','ltc_max_tx_fee','bch_max_tx_fee',
|
||||
'max_tx_file_size'
|
||||
)
|
||||
env_opts = (
|
||||
'MMGEN_BOGUS_WALLET_DATA',
|
||||
|
|
@ -148,6 +149,7 @@ class g(object):
|
|||
|
||||
min_screen_width = 80
|
||||
minconf = 1
|
||||
max_tx_file_size = 100000
|
||||
|
||||
# Global var sets user opt:
|
||||
global_sets_opt = ['minconf','seed_len','hash_preset','usr_randchars','debug',
|
||||
|
|
|
|||
29
mmgen/tx.py
29
mmgen/tx.py
|
|
@ -507,14 +507,6 @@ class MMGenTX(MMGenObject):
|
|||
for e in getattr(self,desc):
|
||||
if hasattr(e,attr): delattr(e,attr)
|
||||
|
||||
def decode_io(self,desc,data):
|
||||
io,il = (
|
||||
(MMGenTX.MMGenTxOutput,MMGenTX.MMGenTxOutputList),
|
||||
(MMGenTX.MMGenTxInput,MMGenTX.MMGenTxInputList)
|
||||
)[desc=='inputs']
|
||||
return il([io(**dict([(k,d[k]) for k in io.__dict__
|
||||
if k in d and d[k] not in ('',None)])) for d in data])
|
||||
|
||||
def decode_io_oldfmt(self,data):
|
||||
tr = {'amount':'amt', 'address':'addr', 'confirmations':'confs','comment':'label'}
|
||||
tr_rev = dict([(v,k) for k,v in tr.items()])
|
||||
|
|
@ -581,6 +573,9 @@ class MMGenTX(MMGenObject):
|
|||
self.chksum = make_chksum_6(' '.join(lines))
|
||||
self.fmt_data = '\n'.join([self.chksum] + lines)+'\n'
|
||||
|
||||
assert len(self.fmt_data) <= g.max_tx_file_size,(
|
||||
'Transaction file size exceeds limit ({} bytes)'.format(g.max_tx_file_size))
|
||||
|
||||
def get_non_mmaddrs(self,desc):
|
||||
return list(set(i.addr for i in getattr(self,desc) if not i.mmid))
|
||||
|
||||
|
|
@ -971,8 +966,6 @@ class MMGenTX(MMGenObject):
|
|||
|
||||
def parse_tx_file(self,infile,md_only=False,silent_open=False):
|
||||
|
||||
tx_data = get_lines_from_file(infile,self.desc+' data',silent=silent_open)
|
||||
|
||||
def eval_io_data(raw_data,desc):
|
||||
from ast import literal_eval
|
||||
try:
|
||||
|
|
@ -985,19 +978,32 @@ class MMGenTX(MMGenObject):
|
|||
assert type(d) == list,'{} data not a list!'.format(desc)
|
||||
assert len(d),'no {}!'.format(desc)
|
||||
for e in d: e['amt'] = g.proto.coin_amt(e['amt'])
|
||||
return self.decode_io(desc,d)
|
||||
io,io_list = (
|
||||
(MMGenTX.MMGenTxOutput,MMGenTX.MMGenTxOutputList),
|
||||
(MMGenTX.MMGenTxInput,MMGenTX.MMGenTxInputList)
|
||||
)[desc=='inputs']
|
||||
return io_list([io(**e) for e in d])
|
||||
|
||||
tx_data = get_data_from_file(infile,self.desc+' data',silent=silent_open)
|
||||
|
||||
try:
|
||||
desc = 'data'
|
||||
assert len(tx_data) <= g.max_tx_file_size,(
|
||||
'Transaction file size exceeds limit ({} bytes)'.format(g.max_tx_file_size))
|
||||
tx_data = tx_data.decode('ascii').splitlines()
|
||||
assert len(tx_data) >= 5,'number of lines less than 5'
|
||||
assert len(tx_data[0]) == 6,'invalid length of first line'
|
||||
self.chksum = HexStr(tx_data.pop(0),on_fail='raise')
|
||||
assert self.chksum == make_chksum_6(' '.join(tx_data)),'file data does not match checksum'
|
||||
|
||||
if len(tx_data) == 6:
|
||||
assert len(tx_data[-1]) == 64,'invalid coin TxID length'
|
||||
desc = '{} TxID'.format(g.proto.name.capitalize())
|
||||
self.coin_txid = CoinTxID(tx_data.pop(-1),on_fail='raise')
|
||||
|
||||
if len(tx_data) == 5:
|
||||
# rough check: allow for 4-byte utf8 characters + base58 (4 * 11 / 8 = 6 (rounded up))
|
||||
assert len(tx_data[-1]) < MMGenTXLabel.max_len*6,'invalid comment length'
|
||||
c = tx_data.pop(-1)
|
||||
if c != '-':
|
||||
desc = 'encoded comment (not base58)'
|
||||
|
|
@ -1008,6 +1014,7 @@ class MMGenTX(MMGenObject):
|
|||
|
||||
desc = 'number of lines' # four required lines
|
||||
metadata,self.hex,inputs_data,outputs_data = tx_data
|
||||
assert len(metadata) < 60,'invalid metadata length' # rough check
|
||||
metadata = metadata.split()
|
||||
|
||||
if metadata[-1].find('LT=') == 0:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue