@@ -76,6 +76,112 @@ int main() {
7676 REQUIRE (machine.remote_connection_count () == 1 );
7777}
7878
79+ TEST_CASE (" Remote call with guest-zeroed FSBASE must not wedge fork" , " [Remote]" )
80+ {
81+ const auto storage_binary = build_and_load (R"M(
82+ extern long write(int, const void*, unsigned long);
83+ int main() {
84+ return 1234;
85+ }
86+ extern void remote_hello_world() {
87+ write(1, "Hello Remote World!", 19);
88+ }
89+ )M" , " -Wl,-Ttext-segment=0x40400000" );
90+
91+ // Extract storage remote symbols
92+ const std::string command = " objcopy -w --extract-symbol --strip-symbol=!remote* --strip-symbol=* " + storage_binary.first + " storage.syms" ;
93+ FILE * f = popen (command.c_str (), " r" );
94+ if (f == nullptr ) {
95+ throw std::runtime_error (" Unable to extract remote symbols" );
96+ }
97+ pclose (f);
98+
99+ const auto main_binary = build_and_load (R"M(
100+ extern void remote_hello_world();
101+ int main() {
102+ return 2345;
103+ }
104+ extern long test_remote_call() {
105+ remote_hello_world();
106+ return 1;
107+ }
108+ extern long test_zero_fsbase() {
109+ unsigned long fsbase;
110+ __asm__ volatile("rdfsbase %0" : "=r"(fsbase));
111+ /* CR4.FSGSBASE is enabled for guests, so a guest can zero its own
112+ FSBASE right before a remote call. That must not affect the
113+ connection state kept by the host. */
114+ __asm__ volatile("wrfsbase %0" :: "r"(0UL));
115+ remote_hello_world();
116+ __asm__ volatile("wrfsbase %0" :: "r"(fsbase));
117+ return 1;
118+ }
119+ )M" , " -Wl,--just-symbols=storage.syms" );
120+
121+ tinykvm::Machine storage { storage_binary.second , {
122+ .max_mem = 16ULL << 20 , // MB
123+ .vmem_base_address = 1ULL << 30 , // 1GB
124+ } };
125+ storage.setup_linux ({" storage" }, env);
126+ storage.run (4 .0f );
127+ REQUIRE (storage.return_value () == 1234 );
128+
129+ unsigned remote_writes = 0 ;
130+ storage.set_printer ([&] (const char * data, size_t size) {
131+ if (std::string_view{data, size} == " Hello Remote World!" )
132+ remote_writes ++;
133+ });
134+
135+ tinykvm::Machine machine { main_binary.second , {
136+ .max_mem = MAX_MEMORY
137+ } };
138+ machine.setup_linux ({" main" }, env);
139+ machine.remote_connect (storage);
140+ machine.set_remote_allow_page_faults (true );
141+ machine.run (4 .0f );
142+ REQUIRE (machine.return_value () == 2345 );
143+ REQUIRE (!machine.is_remote_connected ());
144+
145+ machine.prepare_copy_on_write (1UL << 20 );
146+ const tinykvm::MachineOptions fork_options {
147+ .max_mem = MAX_MEMORY ,
148+ .max_cow_mem = MAX_COWMEM ,
149+ .split_hugepages = true
150+ };
151+
152+ // A guest that zeroes its FSBASE still performs a real remote call, and
153+ // the disconnect afterwards must still happen.
154+ tinykvm::Machine fork (machine, fork_options);
155+ fork.set_remote_allow_page_faults (true );
156+
157+ fork.vmcall (" test_zero_fsbase" );
158+ REQUIRE (fork.return_value () == 1 );
159+ REQUIRE (remote_writes == 1 );
160+ REQUIRE (!fork.is_remote_connected ());
161+ REQUIRE (fork.remote_connection_count () == 1 );
162+
163+ // The fork must be recyclable: an ordinary remote call after reset works.
164+ fork.reset_to (machine, fork_options);
165+ fork.vmcall (" test_remote_call" );
166+ REQUIRE (fork.return_value () == 1 );
167+ REQUIRE (remote_writes == 2 );
168+ REQUIRE (!fork.is_remote_connected ());
169+ REQUIRE (fork.remote_connection_count () == 2 );
170+
171+ // Control: an identical sequence without wrfsbase.
172+ tinykvm::Machine fork2 (machine, fork_options);
173+ fork2.set_remote_allow_page_faults (true );
174+
175+ fork2.vmcall (" test_remote_call" );
176+ REQUIRE (fork2.return_value () == 1 );
177+ fork2.reset_to (machine, fork_options);
178+ fork2.vmcall (" test_remote_call" );
179+ REQUIRE (fork2.return_value () == 1 );
180+ REQUIRE (remote_writes == 4 );
181+ REQUIRE (!fork2.is_remote_connected ());
182+ REQUIRE (fork2.remote_connection_count () == 2 );
183+ }
184+
79185TEST_CASE (" Fail accessing remote VM directly" , " [Remote]" )
80186{
81187 const auto storage_binary = build_and_load (R"M(
0 commit comments