diff mbox

[for-4.8] tools/oxenstored: Avoid allocating invalid transaction ids

Message ID 1477474461-3843-1-git-send-email-andrew.cooper3@citrix.com (mailing list archive)
State New, archived
Headers show

Commit Message

Andrew Cooper Oct. 26, 2016, 9:34 a.m. UTC
The transaction id of 0 is reserved, meaning "not in a transaction".  It is up
to the xenstored server to allocate transaction ids.  While oxenstored starts
its ids at 1, but insufficient care is taken with truncation cases.

A 32bit oxenstored has an int with 31 bits of width, meaning that the
transaction id will wrap around to 0 after 2 billion transactions.

A 64bit oxenstored has an int with 63 bits of width, meaning that once 4
billion transactions are used, the allocated id will be truncated when written
into the uin32_t field in the ring.  This causes the client to reply with the
truncated id, breaking any further attempt to use any transactions.

Limit all transaction ids to the range between 1 and 0x7ffffffe.  This is the
best which can be done without making oxenstored depend on Stdint or Cstruct,
yet still work for 32bit builds.

Also check that the proposed new transaction id isn't currently in use.  For
the first 2 billion transactions there is no chance of a collision, and after
that, the chance is at most 20 (the default open transaction quota) in 2
billion.

Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
---
CC: Ian Jackson <Ian.Jackson@eu.citrix.com>
CC: Wei Liu <wei.liu2@citrix.com>
CC: David Scott <dave@recoil.org>
---
 tools/ocaml/xenstored/connection.ml | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

Comments

David Scott Oct. 26, 2016, 9:46 a.m. UTC | #1
> On 26 Oct 2016, at 10:34, Andrew Cooper <andrew.cooper3@citrix.com> wrote:
> 
> The transaction id of 0 is reserved, meaning "not in a transaction".  It is up
> to the xenstored server to allocate transaction ids.  While oxenstored starts
> its ids at 1, but insufficient care is taken with truncation cases.
> 
> A 32bit oxenstored has an int with 31 bits of width, meaning that the
> transaction id will wrap around to 0 after 2 billion transactions.
> 
> A 64bit oxenstored has an int with 63 bits of width, meaning that once 4
> billion transactions are used, the allocated id will be truncated when written
> into the uin32_t field in the ring.  This causes the client to reply with the
> truncated id, breaking any further attempt to use any transactions.
> 
> Limit all transaction ids to the range between 1 and 0x7ffffffe.  This is the
> best which can be done without making oxenstored depend on Stdint or Cstruct,
> yet still work for 32bit builds.

Good catch, looks good to me!

> 
> Also check that the proposed new transaction id isn't currently in use.  For
> the first 2 billion transactions there is no chance of a collision, and after
> that, the chance is at most 20 (the default open transaction quota) in 2
> billion.

That makes sense to me. There seems little chance of the hash table filling up when the quota is set to 20 :-)

Acked-by: David Scott <dave@recoil.org>

Cheers,
Dave

> 
> Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
> ---
> CC: Ian Jackson <Ian.Jackson@eu.citrix.com>
> CC: Wei Liu <wei.liu2@citrix.com>
> CC: David Scott <dave@recoil.org>
> ---
> tools/ocaml/xenstored/connection.ml | 15 ++++++++++++---
> 1 file changed, 12 insertions(+), 3 deletions(-)
> 
> diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml
> index b18336f..0b47009 100644
> --- a/tools/ocaml/xenstored/connection.ml
> +++ b/tools/ocaml/xenstored/connection.ml
> @@ -216,14 +216,23 @@ let fire_watch watch path =
> 	let data = Utils.join_by_null [ new_path; watch.token; "" ] in
> 	send_reply watch.con Transaction.none 0 Xenbus.Xb.Op.Watchevent data
> 
> -let find_next_tid con =
> -	let ret = con.next_tid in con.next_tid <- con.next_tid + 1; ret
> +(* Search for a valid unused transaction id. *)
> +let rec valid_transaction_id con proposed_id =
> +	(* Clip proposed_id to the range [1, 0x7ffffffe] *)
> +	let id = if proposed_id <= 0 || proposed_id >= 0x7fffffff then 1 else proposed_id in
> +
> +	if Hashtbl.mem con.transactions id then (
> +		(* Outstanding transaction with this id.  Try the next. *)
> +		valid_transaction_id con (id + 1)
> +	) else
> +		id
> 
> let start_transaction con store =
> 	if !Define.maxtransaction > 0 && not (is_dom0 con)
> 	&& Hashtbl.length con.transactions > !Define.maxtransaction then
> 		raise Quota.Transaction_opened;
> -	let id = find_next_tid con in
> +	let id = valid_transaction_id con con.next_tid in
> +	con.next_tid <- id + 1;
> 	let ntrans = Transaction.make id store in
> 	Hashtbl.add con.transactions id ntrans;
> 	Logging.start_transaction ~tid:id ~con:(get_domstr con);
> -- 
> 2.1.4
>
Wei Liu Oct. 26, 2016, 12:02 p.m. UTC | #2
On Wed, Oct 26, 2016 at 10:46:17AM +0100, David Scott wrote:
> 
> > On 26 Oct 2016, at 10:34, Andrew Cooper <andrew.cooper3@citrix.com> wrote:
> > 
> > The transaction id of 0 is reserved, meaning "not in a transaction".  It is up
> > to the xenstored server to allocate transaction ids.  While oxenstored starts
> > its ids at 1, but insufficient care is taken with truncation cases.
> > 
> > A 32bit oxenstored has an int with 31 bits of width, meaning that the
> > transaction id will wrap around to 0 after 2 billion transactions.
> > 
> > A 64bit oxenstored has an int with 63 bits of width, meaning that once 4
> > billion transactions are used, the allocated id will be truncated when written
> > into the uin32_t field in the ring.  This causes the client to reply with the
> > truncated id, breaking any further attempt to use any transactions.
> > 
> > Limit all transaction ids to the range between 1 and 0x7ffffffe.  This is the
> > best which can be done without making oxenstored depend on Stdint or Cstruct,
> > yet still work for 32bit builds.
> 
> Good catch, looks good to me!
> 
> > 
> > Also check that the proposed new transaction id isn't currently in use.  For
> > the first 2 billion transactions there is no chance of a collision, and after
> > that, the chance is at most 20 (the default open transaction quota) in 2
> > billion.
> 
> That makes sense to me. There seems little chance of the hash table filling up when the quota is set to 20 :-)
> 
> Acked-by: David Scott <dave@recoil.org>
> 

Thanks for the quick turnaround.

Applied.
Andrew Cooper Oct. 26, 2016, 12:05 p.m. UTC | #3
On 26/10/16 13:02, Wei Liu wrote:
> On Wed, Oct 26, 2016 at 10:46:17AM +0100, David Scott wrote:
>>> On 26 Oct 2016, at 10:34, Andrew Cooper <andrew.cooper3@citrix.com> wrote:
>>>
>>> The transaction id of 0 is reserved, meaning "not in a transaction".  It is up
>>> to the xenstored server to allocate transaction ids.  While oxenstored starts
>>> its ids at 1, but insufficient care is taken with truncation cases.
>>>
>>> A 32bit oxenstored has an int with 31 bits of width, meaning that the
>>> transaction id will wrap around to 0 after 2 billion transactions.
>>>
>>> A 64bit oxenstored has an int with 63 bits of width, meaning that once 4
>>> billion transactions are used, the allocated id will be truncated when written
>>> into the uin32_t field in the ring.  This causes the client to reply with the
>>> truncated id, breaking any further attempt to use any transactions.
>>>
>>> Limit all transaction ids to the range between 1 and 0x7ffffffe.  This is the
>>> best which can be done without making oxenstored depend on Stdint or Cstruct,
>>> yet still work for 32bit builds.
>> Good catch, looks good to me!
>>
>>> Also check that the proposed new transaction id isn't currently in use.  For
>>> the first 2 billion transactions there is no chance of a collision, and after
>>> that, the chance is at most 20 (the default open transaction quota) in 2
>>> billion.
>> That makes sense to me. There seems little chance of the hash table filling up when the quota is set to 20 :-)
>>
>> Acked-by: David Scott <dave@recoil.org>
>>
> Thanks for the quick turnaround.

Ian: It occurs to me that this should also be a backport candidate to
older trees.

~Andrew
diff mbox

Patch

diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml
index b18336f..0b47009 100644
--- a/tools/ocaml/xenstored/connection.ml
+++ b/tools/ocaml/xenstored/connection.ml
@@ -216,14 +216,23 @@  let fire_watch watch path =
 	let data = Utils.join_by_null [ new_path; watch.token; "" ] in
 	send_reply watch.con Transaction.none 0 Xenbus.Xb.Op.Watchevent data
 
-let find_next_tid con =
-	let ret = con.next_tid in con.next_tid <- con.next_tid + 1; ret
+(* Search for a valid unused transaction id. *)
+let rec valid_transaction_id con proposed_id =
+	(* Clip proposed_id to the range [1, 0x7ffffffe] *)
+	let id = if proposed_id <= 0 || proposed_id >= 0x7fffffff then 1 else proposed_id in
+
+	if Hashtbl.mem con.transactions id then (
+		(* Outstanding transaction with this id.  Try the next. *)
+		valid_transaction_id con (id + 1)
+	) else
+		id
 
 let start_transaction con store =
 	if !Define.maxtransaction > 0 && not (is_dom0 con)
 	&& Hashtbl.length con.transactions > !Define.maxtransaction then
 		raise Quota.Transaction_opened;
-	let id = find_next_tid con in
+	let id = valid_transaction_id con con.next_tid in
+	con.next_tid <- id + 1;
 	let ntrans = Transaction.make id store in
 	Hashtbl.add con.transactions id ntrans;
 	Logging.start_transaction ~tid:id ~con:(get_domstr con);