HN user

prattmic

231 karma

At Google working on the Go runtime and compiler.

Posts1
Comments48
View on HN

“His specialty was alfalfa, and he made a good thing out of not growing any. The government paid him well for every bushel of alfalfa he did not grow. The more alfalfa he did not grow, the more money the government gave him, and he spent every penny he didn't earn on new land to increase the amount of alfalfa he did not produce. Major Major's father worked without rest at not growing alfalfa. On long winter evenings he remained indoors and did not mend harness, and he sprang out of bed at the crack of noon every day just to make certain that the chores would not be done. He invested in land wisely and soon was not growing more alfalfa than any other man in the county. Neighbours sought him out for advice on all subjects, for he had made much money and was therefore wise. “As ye sow, so shall ye reap,” he counselled one and all, and everyone said “Amen.”

-Joseph Heller, Catch-22

We changed the way we track Go threads in syscalls/cgo calls, which allowed us to remove one atomic store and one atomic compare-and-swap from the cgo call path. https://go.dev/cl/646198 and https://go.dev/cl/708596 are the relevant changes.

(It's basically an easter egg, but if you look at the source of the release notes, you will see that most entries in the release notes have an HTML comment referencing the Go CL or issue that the entry refers to.)

How does this work when using a US credit card in the EU? I assume the merchant still pays the lower interchange fee, so are the banks just betting that customers won’t do a large proportion of spending abroad?

2. Cell service has, at the same time, become ubiquitous in subway tunnels.

Not in New York, unfortunately. All of the stations have cell service, and one tunnel (14th Street L train tunnel under the East River), but everywhere else has no service between stations. It’s an annoying limitation that most cities seem to have fixed by now.

In addition to the DoS aspect mentioned in a sibling comment, the primary reason you would do this is to avoid constraining the implementation. If you want to change the design to improve performance, for example, being forced to match the implicit ordering of the old implementation may be very difficult.

It certainly may be useful to define an specific ordering. Maps ordered by insertion time and maps ordered by key order are fairly common. But maps with these constraints probably won't be as fast as those with arbitrary ordering, so there is a trade-off decision to be made. A map constrained to the arbitrary ordering of the initial implementation is the worst of both worlds: difficult to make faster despite not having a particularly useful ordering.

As a concrete example, I am currently in the middle of rewriting Go's map implementation to use a more efficient design (https://go.dev/issue/54766). If Go hadn't randomized map iteration order this work would likely be impossible. It is unlikely we could completely change the design while keeping iteration order identical to the old implementation (assuming we want the new version to be faster at least).

I think the closest analog to this is digital services getting worse rather than more expensive. For example, recently rather than increasing the price of Amazon Prime Video, the price has remained the same, but the product is worse (now has ads).

That said, I don’t think this bill would target something like that, as it clearly discusses the “size” and “amount” of a product.

Jetbrains Space 4 years ago

Gerrit is pretty strongly opinionated, but it is not mailing-list based. Review notifications are sent via email, but otherwise everything needs to be done via the web UI (or other tool using the API).

Jetbrains Space 4 years ago

I’m not familiar with Upsource, but what you describe sounds somewhat like the Gerrit review process. Each commit is reviewed individually, but sending multiple dependent commits for review is supported as well. It is technically possible for a coworker to build on top of your unsubmitted changes (by checking out your commits and adding more on top). However this process gets somewhat painful if the coworker updates their commits, which would require you to do a convoluted rebase.

The benefits come primarily from avoiding extra work spilling arguments to/from the stack on function calls. If you are making lots and lots of function calls, particularly to small functions that can't be inlined, there could certainly be much bigger improvements.

This is awesome! In the post you mention "For a couple hundred lines of code (not counting the entire user-mode Linux you’ll be pulling in from gVisor, HEY! Dependencies! What are you gonna do!) ..."

I'll note that while all of gVisor's user-mode Linux is in the same Go module, we've actually gone to decent lengths to keep the network stack logically separate from the rest of the user-mode Linux code.

So while go.sum might look a bit frightening, Brad's depaware shows that the extra code you pull in to binaries by using netstack is actually quite minimal: https://github.com/tailscale/tailscale/commit/5aa5db89d6a9a6....

I work on gVisor, I can answer this!

gVisor is not a rewritten version of an internal tool. The code you see really does run in production for App Engine and Cloud Run. While there are some internal modifications to better integrate with internal infrastructure, the vast majority of the code is identical to open source, critically including all of the system call handling, filesystem, and memory management code.

While browsing through our issues will show that we still have plenty to work on, the vast majority of applications work well inside gVisor.

For sure, implementing Linux is no easy task, and there is no magic bullet. For compatibility testing, we have extensive system call unit tests [1] and also run many open source test suites. Language runtime tests (e.g., Python, Go, etc) are particularly useful. We also perform continuous fuzzing with Syzkaller [2].

how do you implement features that ostensibly require calls to the OS anyways?

gVisor's kernel is a user-space program, so it can and does make system calls to the host OS. Some examples:

* An application blocks trying to read(2) from a pipe. gVisor ultimately implements blocking by waiting on a Go channel. The Go runtime will ultimately implement this with a futex(2) call to the host OS. * An application reads from a file that is ultimately backed by a file on the host (provided by the Gofer [3]). This will result in a pread(2) system call to the host.

The purpose here isn't to avoid the host completely (that's not possible), but to limit exposure to the host. gVisor can implement all the parts of Linux it does on a much smaller subset of host system calls. Anything we don't use is blocked by a second-level seccomp sandbox around the kernel. e.g., the kernel cannot make obscure system calls, or even open files or create sockets on the host (those operations are controlled by an external agent).

[1] https://github.com/google/gvisor/tree/master/test/syscalls/l...

[2] https://github.com/google/syzkaller

[3] https://gvisor.dev/docs/architecture_guide/overview/

From what I understand, basically a user-space program that wraps your container and intercepts all system calls. You can then allow/deny/re-wire them (based on a config).

gVisor actually intercepts and implements the system calls in the user-space kernel. Two specific goals of gVisor are that (1) system calls are never simply allowed and passed through to the host kernel, and (2) you don't need to write a policy configuration for your application; just put your application inside gVisor and go. These are significant differences over simply using something like seccomp on its own (what the architecture guide calls "Rule-based execution").

Some of this is covered in our security model: https://gvisor.dev/docs/architecture_guide/security/#princip...

As someone that works on gVisor, I disagree with this w.r.t. our project. i.e., the existence of our go_generics tool is not an obvious indication that we need generics in the language.

A bit of history: this tool was originally created specifically for two packages, a linked list package, and a tree-based data-structure package. Both of these used an interface for the entry types, but this had two main drawbacks:

1. Interface assertions and conversions are (often) not free. These packages are used heavily in our critical system call handling path and this was costing on the order of a couple hundred nanoseconds in the critical path (total cost of the critical path is ~1500-4000ns depending on the syscall).

2. Escape analysis does not work across interface methods, requiring heap allocation of pointer arguments. This was less of an immediate problem for the initial creation of go_generics, but annoying to work around in the critical code where it mattered.

The go_generics tool solves both of these problems by generating versions of these data structures with concrete types. However, neither of these problems require generics to solve. General compiler and toolchain improvements could solve both. In fact, I imagine that (1) is greatly improved already (we were solving this problem in the Go 1.5 timeframe).

To this day, our code base has a grand total of 5 generic templates:

segment [1], ilist [2]: These are the two packages referenced above.

seqatomic [3]: Synchronization primitive for sequence count protected values. Not safe to use interfaces.

bits [4]: This is the typical "multiple integer types" problems. IMO, this use is overkill, there are only ~20 lines of code to copy.

pagetables Walker [5]: Used in critical //go:nosplit //go:noescape context, where most runtime calls are unsafe.

Given our experience with, and very narrow use of our go_generics tool, I'd actually rather Go not add generics and continue to use specialized code generation in the places we really need it.

I do recognize that there are a lot of other cases where people legitimately want generics and I am not fundamentally opposed to the draft proposal, but this was my long-winded way of saying that our go_generics are not an obvious argument in favor.

[1] https://github.com/google/gvisor/blob/f3060cb1a4ed61199688b5...

[2] https://github.com/google/gvisor/blob/8fee8f4dd5f7257a7eb77c...

[3] https://github.com/google/gvisor/blob/d1bbaf8b2caba906484d5e...

[4] https://github.com/google/gvisor/blob/d1bbaf8b2caba906484d5e...

[5] https://github.com/google/gvisor/blob/d1bbaf8b2caba906484d5e...

There are two primary strengths that gVisor provides over the seccomp model, the second of which you've actually alluded to above.

1. Layered security

While seccomp allows users to limit the attack surface on the kernel, the application is still directly interacting with it and any single bug in an allowed system call will allow compromise. One of the design principles of gVisor is that no single bug should allow compromise of the host system/user data.

By intercepting and handling all application system calls, the gVisor kernel is the first layer of defense against the application. The gVisor kernel itself puts itself inside a seccomp sandbox as a second layer of defense, so if the application gets privilege escalation into the gVisor kernel its attack surface to the host is still limited.

The gVisor kernel seccomp policy [1] is much more restrictive than the system calls we implement. For example, note that "open" and friends are not allowed at all. File system access is mediated by an external agent [2] which does not trust the gVisor kernel, so even a compromised gVisor kernel has no elevated file system access.

2. Ease of use

> Kernel features like seccomp filters can provide better isolation between the application and host kernel, but they require the user to create a predefined whitelist of system calls.

Isn't that something you'd effectively have to do anyway if you want a sandbox?

This is something we'd like to challenge with gVisor. gVisor intends to be "secure by default" and configuration-free to the largest extent possible.

gVisor runs and sandboxes arbitrary, unmodified Linux binaries. You don't need to specify a sandbox policy because gVisor safely implements the entire Linux API [3].

Building a sandbox policy can be a difficult and time consuming. It can also be a difficult maintenance burden to update as the application changes over time, especially if you've made modifications to the application to reduce its syscall surface. Additionally, some use-cases wish to sandbox arbitrary workloads, for which a sandbox policy cannot be defined.

With gVisor, we hope to remove this painful step in sandboxing and enable developers to easily sandbox their workloads.

(Note: I work on gVisor.)

[1] https://github.com/google/gvisor/blob/master/runsc/boot/filt...

[2] https://github.com/google/gvisor#file-system-access

[3] Note we don't technically fully implement Linux, as work is ongoing, but missing features are simply unimplemented, not left out for security reasons. See https://github.com/google/gvisor#will-my-container-work-with...