Change the rseq ABI so rseq_cs start_ip, post_commit_offset and abort_ip
fields are seen as 64-bit fields by both 32-bit and 64-bit kernels rather
that ignoring the 32 upper bits on 32-bit kernels. This ensures we have a
consistent behavior for a 32-bit binary executed on 32-bit kernels and in
compat mode on 64-bit kernels.
Validating the value of abort_ip field to be below TASK_SIZE ensures the
kernel don't return to an invalid address when returning to userspace
after an abort. I don't fully trust each architecture code to consistently
deal with invalid return addresses.
Validating the value of the start_ip and post_commit_offset fields
prevents overflow on arithmetic performed on those values, used to
check whether abort_ip is within the rseq critical section.
If validation fails, the process is killed with a segmentation fault.
When the signature encountered before abort_ip does not match the expected
signature, return -EINVAL rather than -EPERM to be consistent with other
input validation return codes from rseq_get_rseq_cs().
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
CC: "Paul E. McKenney" <redacted>
CC: Peter Zijlstra <peterz@infradead.org>
CC: Paul Turner <redacted>
CC: Thomas Gleixner <redacted>
CC: Andy Lutomirski <luto@amacapital.net>
CC: Andi Kleen <redacted>
CC: Dave Watson <redacted>
CC: Chris Lameter <redacted>
CC: Ingo Molnar <mingo@redhat.com>
CC: "H. Peter Anvin" <hpa@zytor.com>
CC: Ben Maurer <redacted>
CC: Steven Rostedt <rostedt@goodmis.org>
CC: Josh Triplett <josh@joshtriplett.org>
CC: Linus Torvalds <torvalds@linux-foundation.org>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: Russell King <redacted>
CC: Catalin Marinas <catalin.marinas@arm.com>
CC: Will Deacon <redacted>
CC: Michael Kerrisk <redacted>
CC: Boqun Feng <redacted>
CC: linux-api@vger.kernel.org
---
include/uapi/linux/rseq.h | 6 +++---
kernel/rseq.c | 14 ++++++++++----
2 files changed, 13 insertions(+), 7 deletions(-)
@@ -130,14 +130,20 @@ static int rseq_get_rseq_cs(struct task_struct *t, struct rseq_cs *rseq_cs)urseq_cs=(structrseq_cs__user*)ptr;if(copy_from_user(rseq_cs,urseq_cs,sizeof(*rseq_cs)))return-EFAULT;-if(rseq_cs->version>0)-return-EINVAL;+if(rseq_cs->start_ip>=TASK_SIZE||+rseq_cs->start_ip+rseq_cs->post_commit_offset>=TASK_SIZE||+rseq_cs->abort_ip>=TASK_SIZE||+rseq_cs->version>0)+return-EINVAL;+/* Check for overflow. */+if(rseq_cs->start_ip+rseq_cs->post_commit_offset<rseq_cs->start_ip)+return-EINVAL;/* Ensure that abort_ip is not in the critical section. */if(rseq_cs->abort_ip-rseq_cs->start_ip<rseq_cs->post_commit_offset)return-EINVAL;-usig=(u32__user*)(rseq_cs->abort_ip-sizeof(u32));+usig=(u32__user*)(unsignedlong)(rseq_cs->abort_ip-sizeof(u32));ret=get_user(sig,usig);if(ret)returnret;
This header was introduced in the 4.18 merge window, and rseq does
not need it anymore. Nuke it before the final release.
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
CC: "Paul E. McKenney" <redacted>
CC: Peter Zijlstra <peterz@infradead.org>
CC: Paul Turner <redacted>
CC: Thomas Gleixner <redacted>
CC: Andy Lutomirski <luto@amacapital.net>
CC: Andi Kleen <redacted>
CC: Dave Watson <redacted>
CC: Chris Lameter <redacted>
CC: Ingo Molnar <mingo@redhat.com>
CC: "H. Peter Anvin" <hpa@zytor.com>
CC: Ben Maurer <redacted>
CC: Steven Rostedt <rostedt@goodmis.org>
CC: Josh Triplett <josh@joshtriplett.org>
CC: Linus Torvalds <torvalds@linux-foundation.org>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: Russell King <redacted>
CC: Catalin Marinas <catalin.marinas@arm.com>
CC: Will Deacon <redacted>
CC: Michael Kerrisk <redacted>
CC: Boqun Feng <redacted>
CC: linux-api@vger.kernel.org
---
include/uapi/linux/types_32_64.h | 50 ----------------------------------------
1 file changed, 50 deletions(-)
delete mode 100644 include/uapi/linux/types_32_64.h
rseq as it was merged does not have rseq_finish_*() in the user-space
selftests anymore. Update the rseq_prepare_unload() helper comment to
adapt to this reality.
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
CC: "Paul E. McKenney" <redacted>
CC: Peter Zijlstra <peterz@infradead.org>
CC: Paul Turner <redacted>
CC: Thomas Gleixner <redacted>
CC: Andy Lutomirski <luto@amacapital.net>
CC: Andi Kleen <redacted>
CC: Dave Watson <redacted>
CC: Chris Lameter <redacted>
CC: Ingo Molnar <mingo@redhat.com>
CC: "H. Peter Anvin" <hpa@zytor.com>
CC: Ben Maurer <redacted>
CC: Steven Rostedt <rostedt@goodmis.org>
CC: Josh Triplett <josh@joshtriplett.org>
CC: Linus Torvalds <torvalds@linux-foundation.org>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: Russell King <redacted>
CC: Catalin Marinas <catalin.marinas@arm.com>
CC: Will Deacon <redacted>
CC: Michael Kerrisk <redacted>
CC: Boqun Feng <redacted>
CC: linux-api@vger.kernel.org
---
tools/testing/selftests/rseq/rseq.h | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
Update rseq uapi header comments to reflect that user-space need to do
thread-local loads/stores from/to the struct rseq fields.
As a consequence of this added requirement, the kernel does not need
to perform loads/stores with single-copy atomicity.
Update the comment associated to the "flags" fields to describe
more accurately that it's only useful to facilitate single-stepping
through rseq critical sections with debuggers.
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
CC: "Paul E. McKenney" <redacted>
CC: Peter Zijlstra <peterz@infradead.org>
CC: Paul Turner <redacted>
CC: Thomas Gleixner <redacted>
CC: Andy Lutomirski <luto@amacapital.net>
CC: Andi Kleen <redacted>
CC: Dave Watson <redacted>
CC: Chris Lameter <redacted>
CC: Ingo Molnar <mingo@redhat.com>
CC: "H. Peter Anvin" <hpa@zytor.com>
CC: Ben Maurer <redacted>
CC: Steven Rostedt <rostedt@goodmis.org>
CC: Josh Triplett <josh@joshtriplett.org>
CC: Linus Torvalds <torvalds@linux-foundation.org>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: Russell King <redacted>
CC: Catalin Marinas <catalin.marinas@arm.com>
CC: Will Deacon <redacted>
CC: Michael Kerrisk <redacted>
CC: Boqun Feng <redacted>
CC: linux-api@vger.kernel.org
---
include/uapi/linux/rseq.h | 69 ++++++++++++++++++++++++-----------------------
1 file changed, 36 insertions(+), 33 deletions(-)
@@ -67,28 +67,30 @@ struct rseq_cs {structrseq{/**Restartablesequencescpu_id_startfield.Updatedbythe-*kernel,andreadbyuser-spacewithsingle-copyatomicity-*semantics.Alignedon32-bit.Alwayscontainsavalueinthe-*rangeofpossibleCPUs,althoughthevaluemaynotbethe-*actualcurrentCPU(e.g.ifrseqisnotinitialized).This-*CPUnumbervalueshouldalwaysbecomparedagainstthevalue-*ofthecpu_idfieldbeforeperformingarseqcommitor-*returningavaluereadfromadatastructureindexedusing-*thecpu_id_startvalue.+*kernel.Readbyuser-spacewithsingle-copyatomicity+*semantics.Thisfieldshouldonlybereadbythethreadwhich+*registeredthisdatastructure.Alignedon32-bit.Always+*containsavalueintherangeofpossibleCPUs,althoughthe+*valuemaynotbetheactualcurrentCPU(e.g.ifrseqisnot+*initialized).ThisCPUnumbervalueshouldalwaysbecompared+*againstthevalueofthecpu_idfieldbeforeperformingarseq+*commitorreturningavaluereadfromadatastructureindexed+*usingthecpu_id_startvalue.*/__u32cpu_id_start;/*-*Restartablesequencescpu_idfield.Updatedbythekernel,-*andreadbyuser-spacewithsingle-copyatomicitysemantics.-*Alignedon32-bit.ValuesRSEQ_CPU_ID_UNINITIALIZEDand-*RSEQ_CPU_ID_REGISTRATION_FAILEDhaveaspecialsemantic:the-*formermeans"rseq uninitialized",andlattermeans"rseq-*initializationfailed". This value is meant to be read within-*rseqcriticalsectionsandcomparedwiththecpu_id_start-*valuepreviouslyread,beforeperformingthecommitinstruction,-*orreadandcomparedwiththecpu_id_startvaluebeforereturning-*avalueloadedfromadatastructureindexedusingthe-*cpu_id_startvalue.+*Restartablesequencescpu_idfield.Updatedbythekernel.+*Readbyuser-spacewithsingle-copyatomicitysemantics.This+*fieldshouldonlybereadbythethreadwhichregisteredthis+*datastructure.Alignedon32-bit.Values+*RSEQ_CPU_ID_UNINITIALIZEDandRSEQ_CPU_ID_REGISTRATION_FAILED+*haveaspecialsemantic:theformermeans"rseq uninitialized",+*andlattermeans"rseq initialization failed".Thisvalueis+*meanttobereadwithinrseqcriticalsectionsandcompared+*withthecpu_id_startvaluepreviouslyread,beforeperforming+*thecommitinstruction,orreadandcomparedwiththe+*cpu_id_startvaluebeforereturningavalueloadedfromadata+*structureindexedusingthecpu_id_startvalue.*/__u32cpu_id;/*
Declaring the rseq_cs field as a union between __u64 and two __u32
allows both 32-bit and 64-bit kernels to read the full __u64, and
therefore validate that a 32-bit user-space cleared the upper 32
bits, thus ensuring a consistent behavior between native 32-bit
kernels and 32-bit compat tasks on 64-bit kernels.
Check that the rseq_cs value read is < TASK_SIZE.
The asm/byteorder.h header needs to be included by rseq.h, now
that it is not using linux/types_32_64.h anymore.
Considering that only __32 and __u64 types are declared in linux/rseq.h,
the linux/types.h header should always be included for both kernel and
user-space code: including stdint.h is just for u64 and u32, which are
not used in this header at all.
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
CC: "Paul E. McKenney" <redacted>
CC: Peter Zijlstra <peterz@infradead.org>
CC: Paul Turner <redacted>
CC: Thomas Gleixner <redacted>
CC: Andy Lutomirski <luto@amacapital.net>
CC: Andi Kleen <redacted>
CC: Dave Watson <redacted>
CC: Chris Lameter <redacted>
CC: Ingo Molnar <mingo@redhat.com>
CC: "H. Peter Anvin" <hpa@zytor.com>
CC: Ben Maurer <redacted>
CC: Steven Rostedt <rostedt@goodmis.org>
CC: Josh Triplett <josh@joshtriplett.org>
CC: Linus Torvalds <torvalds@linux-foundation.org>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: Russell King <redacted>
CC: Catalin Marinas <catalin.marinas@arm.com>
CC: Will Deacon <redacted>
CC: Michael Kerrisk <redacted>
CC: Boqun Feng <redacted>
CC: linux-api@vger.kernel.org
---
include/uapi/linux/rseq.h | 27 +++++++++++++++++++--------
kernel/rseq.c | 12 +++++++-----
tools/testing/selftests/rseq/rseq.h | 11 ++++++++++-
3 files changed, 36 insertions(+), 14 deletions(-)
----- On Jul 5, 2018, at 2:05 PM, Mathieu Desnoyers mathieu.desnoyers@efficios.com wrote:
Declaring the rseq_cs field as a union between __u64 and two __u32
allows both 32-bit and 64-bit kernels to read the full __u64, and
therefore validate that a 32-bit user-space cleared the upper 32
bits, thus ensuring a consistent behavior between native 32-bit
kernels and 32-bit compat tasks on 64-bit kernels.
Check that the rseq_cs value read is < TASK_SIZE.
The asm/byteorder.h header needs to be included by rseq.h, now
that it is not using linux/types_32_64.h anymore.
Considering that only __32 and __u64 types are declared in linux/rseq.h,
the linux/types.h header should always be included for both kernel and
user-space code: including stdint.h is just for u64 and u32, which are
not used in this header at all.
The 0-day bot noticed that __get_user() is unimplemented for 64-bit
values on arm32 (although get_user() is implemented).
The following diff fixes this discrepancy, and allows this rseq patch
to build on arm32:
commit dde99f3310c76acb0a160c0572f40b6aa279594c
Author: Mathieu Desnoyers [off-list ref]
Date: Fri Jul 6 11:29:39 2018 -0400
arm: implement 64-bit __get_user
get_user() is implemented on arm32 for 64-bit user-space values, but
not its __get_user() counterpart.
Implement __get_user() as two __get_user_asm_word().
Signed-off-by: Mathieu Desnoyers [off-list ref]
CC: "Paul E. McKenney" [off-list ref]
CC: Peter Zijlstra [off-list ref]
CC: Paul Turner [off-list ref]
CC: Thomas Gleixner [off-list ref]
CC: Andy Lutomirski [off-list ref]
CC: Andi Kleen [off-list ref]
CC: Dave Watson [off-list ref]
CC: Chris Lameter [off-list ref]
CC: Ingo Molnar [off-list ref]
CC: "H. Peter Anvin" [off-list ref]
CC: Ben Maurer [off-list ref]
CC: Steven Rostedt [off-list ref]
CC: Josh Triplett [off-list ref]
CC: Linus Torvalds [off-list ref]
CC: Andrew Morton [off-list ref]
CC: Russell King [off-list ref]
CC: Catalin Marinas [off-list ref]
CC: Will Deacon [off-list ref]
CC: Michael Kerrisk [off-list ref]
CC: Boqun Feng [off-list ref]
CC: linux-api@vger.kernel.org
@@ -115,19 +115,21 @@ static int rseq_reset_rseq_cpu_id(struct task_struct *t)
static int rseq_get_rseq_cs(struct task_struct *t, struct rseq_cs *rseq_cs)
{
struct rseq_cs __user *urseq_cs;
- unsigned long ptr;
+ u64 ptr;
u32 __user *usig;
u32 sig;
int ret;
- ret = __get_user(ptr, &t->rseq->rseq_cs);
+ ret = __get_user(ptr, &t->rseq->rseq_cs.ptr64);
if (ret)
return ret;
if (!ptr) {
memset(rseq_cs, 0, sizeof(*rseq_cs));
return 0;
}
- urseq_cs = (struct rseq_cs __user *)ptr;
+ if (ptr >= TASK_SIZE)
+ return -EINVAL;
+ urseq_cs = (struct rseq_cs __user *)(unsigned long)ptr;
if (copy_from_user(rseq_cs, urseq_cs, sizeof(*rseq_cs)))
return -EFAULT;
@@ -201,9 +203,9 @@ static int clear_rseq_cs(struct task_struct *t)
* of code outside of the rseq assembly block. This performs
* a lazy clear of the rseq_cs field.
*
- * Set rseq_cs to NULL with single-copy atomicity.
+ * Set rseq_cs to NULL.
*/
- return __put_user(0UL, &t->rseq->rseq_cs);
+ return __put_user(0ULL, &t->rseq->rseq_cs.ptr64);
}
/*
diff --git a/tools/testing/selftests/rseq/rseq.h
b/tools/testing/selftests/rseq/rseq.h
index a4684112676c..f2073cfa4448 100644
return cpu;
}
+static inline void rseq_clear_rseq_cs(void)
+{
+#ifdef __LP64__
+ __rseq_abi.rseq_cs.ptr = 0;
+#else
+ __rseq_abi.rseq_cs.ptr.ptr32 = 0;
+#endif
+}
+
/*
* rseq_prepare_unload() should be invoked by each thread using rseq_finish*()
* at least once between their last rseq_finish*() and library unload of the
----- On Jul 6, 2018, at 12:02 PM, Mathieu Desnoyers mathieu.desnoyers@efficios.com wrote:
----- On Jul 5, 2018, at 2:05 PM, Mathieu Desnoyers
mathieu.desnoyers@efficios.com wrote:
[...]
The 0-day bot noticed that __get_user() is unimplemented for 64-bit
values on arm32 (although get_user() is implemented).
The following diff fixes this discrepancy, and allows this rseq patch
to build on arm32:
For -rc, I would favor the following simpler approach. Or I could even
just use get_user() instead. Thoughts ?
rseq: implement work-around for missing 8-byte __get_user on arm
Now that rseq uses __u64 for its pointer fields, 32-bit architectures
need to read this 64-bit value from user-space.
__get_user is used to read this value, given that its access check has
already been performed with access_ok() on rseq registration.
arm does not implement 8-byte __get_user. Work-around this limitation
by using get_user() on ARM instead, with its redundant access check.
Signed-off-by: Mathieu Desnoyers [off-list ref]
CC: Thomas Gleixner [off-list ref]
Cc: Joel Fernandes [off-list ref]
Cc: Peter Zijlstra [off-list ref]
Cc: Catalin Marinas [off-list ref]
Cc: Dave Watson [off-list ref]
Cc: Will Deacon [off-list ref]
Cc: Andi Kleen [off-list ref]
Cc: "H . Peter Anvin" [off-list ref]
Cc: Chris Lameter [off-list ref]
Cc: Russell King [off-list ref]
Cc: Andrew Hunter [off-list ref]
Cc: Michael Kerrisk [off-list ref]
Cc: "Paul E . McKenney" [off-list ref]
Cc: Paul Turner [off-list ref]
Cc: Boqun Feng [off-list ref]
Cc: Josh Triplett [off-list ref]
Cc: Steven Rostedt [off-list ref]
Cc: Ben Maurer [off-list ref]
Cc: linux-api@vger.kernel.org
CC: linux-arm-kernel@lists.infradead.org
Cc: Andy Lutomirski [off-list ref]
Cc: Andrew Morton [off-list ref]
Cc: Linus Torvalds [off-list ref]
On Fri, Jul 6, 2018 at 12:23 PM Mathieu Desnoyers
[off-list ref] wrote:
For -rc, I would favor the following simpler approach. Or I could even
just use get_user() instead. Thoughts ?
Please just use "get_user()".
In fact, we should be thinking seriosly about just removing
__get_user() entirely. It's wrong. It optimizes the wrong thing
entirely. It _used_ to be that the range check was noticeable, and it
really isn't any more. These days the expensive parts are the SMAP
costs, and both get_user() and __get_user() have those, except
get_user() is safer and doesn't waste I$ on inlining the code to
disable and re-enable SMAP.
Linus
----- On Jul 6, 2018, at 3:31 PM, Linus Torvalds torvalds@linux-foundation.org wrote:
On Fri, Jul 6, 2018 at 12:23 PM Mathieu Desnoyers
[off-list ref] wrote:
quoted
For -rc, I would favor the following simpler approach. Or I could even
just use get_user() instead. Thoughts ?
Please just use "get_user()".
In fact, we should be thinking seriosly about just removing
__get_user() entirely. It's wrong. It optimizes the wrong thing
entirely. It _used_ to be that the range check was noticeable, and it
really isn't any more. These days the expensive parts are the SMAP
costs, and both get_user() and __get_user() have those, except
get_user() is safer and doesn't waste I$ on inlining the code to
disable and re-enable SMAP.
----- On Jul 6, 2018, at 3:35 PM, Mathieu Desnoyers mathieu.desnoyers@efficios.com wrote:
----- On Jul 6, 2018, at 3:31 PM, Linus Torvalds torvalds@linux-foundation.org
wrote:
quoted
On Fri, Jul 6, 2018 at 12:23 PM Mathieu Desnoyers
[off-list ref] wrote:
quoted
For -rc, I would favor the following simpler approach. Or I could even
just use get_user() instead. Thoughts ?
Please just use "get_user()".
In fact, we should be thinking seriosly about just removing
__get_user() entirely. It's wrong. It optimizes the wrong thing
entirely. It _used_ to be that the range check was noticeable, and it
really isn't any more. These days the expensive parts are the SMAP
costs, and both get_user() and __get_user() have those, except
get_user() is safer and doesn't waste I$ on inlining the code to
disable and re-enable SMAP.
Will do, thanks!
Should I change all 4 bytes __get_user()/__put_user() in kernel/rseq.c
for get_user()/put_user() to ensure consistency ?
Thanks,
Mathieu
From: Andy Lutomirski <luto@kernel.org> Date: 2018-07-06 19:56:42
On Fri, Jul 6, 2018 at 12:31 PM, Linus Torvalds
[off-list ref] wrote:
On Fri, Jul 6, 2018 at 12:23 PM Mathieu Desnoyers
[off-list ref] wrote:
quoted
For -rc, I would favor the following simpler approach. Or I could even
just use get_user() instead. Thoughts ?
Please just use "get_user()".
In fact, we should be thinking seriosly about just removing
__get_user() entirely. It's wrong. It optimizes the wrong thing
entirely. It _used_ to be that the range check was noticeable, and it
really isn't any more. These days the expensive parts are the SMAP
costs, and both get_user() and __get_user() have those, except
get_user() is safer and doesn't waste I$ on inlining the code to
disable and re-enable SMAP.
If Al and Christoph ever manage to get rid of set_fs(), I bet we can
rewrite access_ok() and get_user() so that gcc can fold redundant
checks together and generate optimal code for get_user() of
consecutive struct fields all by itself. Or maybe I'm giving gcc more
credit than it deserves.
On Fri, Jul 6, 2018 at 12:38 PM Mathieu Desnoyers
[off-list ref] wrote:
Should I change all 4 bytes __get_user()/__put_user() in kernel/rseq.c
for get_user()/put_user() to ensure consistency ?
Probably.
*If* this actually turns out to be somethinig that shows up on
profiles, it's almost certainly going to be the STAC/CLAC instructions
("perf report" tends to report them as three one-byte nop's because
that's how they look before instruction replacement).
And then it's not __get/put_user() that will improve things, but doing a
user_access_begin();
.. do unsafe_get/put_user() ..
user_access_end();
that will improve performance.
But it is *very* seldom useful. We have it in a handful of places in
the kernel, and the most noticeable one is
lib/{strnlen,strncpy_from}_user.c
Linus
From: Russell King - ARM Linux <linux@armlinux.org.uk> Date: 2018-07-07 15:07:14
On Fri, Jul 06, 2018 at 12:56:58PM -0700, Linus Torvalds wrote:
On Fri, Jul 6, 2018 at 12:38 PM Mathieu Desnoyers
[off-list ref] wrote:
quoted
Should I change all 4 bytes __get_user()/__put_user() in kernel/rseq.c
for get_user()/put_user() to ensure consistency ?
Probably.
*If* this actually turns out to be somethinig that shows up on
profiles, it's almost certainly going to be the STAC/CLAC instructions
("perf report" tends to report them as three one-byte nop's because
that's how they look before instruction replacement).
And then it's not __get/put_user() that will improve things, but doing a
user_access_begin();
.. do unsafe_get/put_user() ..
user_access_end();
that will improve performance.
But it is *very* seldom useful. We have it in a handful of places in
the kernel, and the most noticeable one is
lib/{strnlen,strncpy_from}_user.c
Also, __get_user() is probably going to become the same as get_user()
when I finish the Spectre v1 ARM mitigations, because there'll be no
point in __get_user() being any different. For those mitigations,
we're going to have to check the pointer against the address limit
inside __get_user() and NULL it out, just like get_user() does, which
makes the whole distinction between the two completely pointless.
Is this not also the case on other architectures affected by Spectre
variant 1, hmm?
--
RMK's Patch system: http://www.armlinux.org.uk/developer/patches/
FTTC broadband for 0.8mile line in suburbia: sync at 13.8Mbps down 630kbps up
According to speedtest.net: 13Mbps down 490kbps up