meek_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. /*
  2. * Copyright (c) 2025, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package protocol
  20. import (
  21. "bytes"
  22. "io"
  23. "testing"
  24. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  25. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
  26. )
  27. func TestMeekPayloadPadding(t *testing.T) {
  28. err := runTestMeekPayloadPadding()
  29. if err != nil {
  30. t.Fatal(err.Error())
  31. return
  32. }
  33. }
  34. func runTestMeekPayloadPadding() error {
  35. key := prng.HexString(16)
  36. cookie := prng.HexString(16)
  37. // Test: invalid configurations
  38. _, err := NewMeekRequestPayloadPaddingState(key, cookie, 0.0, -1, 0)
  39. if err == nil {
  40. return errors.TraceNew("unexpected success")
  41. }
  42. _, err = NewMeekRequestPayloadPaddingState(key, cookie, 0.0, 2, 1)
  43. if err == nil {
  44. return errors.TraceNew("unexpected success")
  45. }
  46. _, err = NewMeekRequestPayloadPaddingState(key, cookie, 0.0, 0, 65534)
  47. if err == nil {
  48. return errors.TraceNew("unexpected success")
  49. }
  50. // Test: immediate EOF
  51. receiver, err := NewMeekRequestPayloadPaddingState(key, cookie, 0.0, 0, 0)
  52. if err != nil {
  53. return errors.Trace(err)
  54. }
  55. bytesRead, morePadding, err := receiver.ReceiverConsumePadding(
  56. bytes.NewReader(nil))
  57. if bytesRead != 0 || morePadding || err != ErrMeekPaddingStateImmediateEOF {
  58. return errors.TraceNew("unexpected consume return values")
  59. }
  60. // Test: unknown prefix
  61. sender, err := NewMeekRequestPayloadPaddingState(key, cookie, 0.0, 1, 1)
  62. if err != nil {
  63. return errors.Trace(err)
  64. }
  65. receiver, err = NewMeekRequestPayloadPaddingState(key, cookie, 0.0, 0, 0)
  66. if err != nil {
  67. return errors.Trace(err)
  68. }
  69. paddingHeader, err := sender.SenderGetNextPadding(false)
  70. if err != nil {
  71. return errors.Trace(err)
  72. }
  73. if len(paddingHeader) != 1 {
  74. return errors.TraceNew("unexpected padding header length")
  75. }
  76. corrupt := append([]byte(nil), paddingHeader...)
  77. corrupt[0] ^= 0x02 // flips decrypted prefix from 0 to 2
  78. bytesRead, morePadding, err = receiver.ReceiverConsumePadding(
  79. bytes.NewReader(corrupt))
  80. if bytesRead != 1 || morePadding || err == nil {
  81. return errors.TraceNew("unexpected consume return values")
  82. }
  83. // Test: incomplete padding size
  84. sender, err = NewMeekRequestPayloadPaddingState(key, cookie, 0.0, 1, 1)
  85. if err != nil {
  86. return errors.Trace(err)
  87. }
  88. receiver, err = NewMeekRequestPayloadPaddingState(key, cookie, 0.0, 0, 0)
  89. if err != nil {
  90. return errors.Trace(err)
  91. }
  92. paddingHeader, err = sender.SenderGetNextPadding(true)
  93. if err != nil {
  94. return errors.Trace(err)
  95. }
  96. if len(paddingHeader) < 3 {
  97. return errors.TraceNew("unexpected padding header length")
  98. }
  99. bytesRead, morePadding, err = receiver.ReceiverConsumePadding(
  100. bytes.NewReader(paddingHeader[:1]))
  101. if bytesRead != 1 || !morePadding || err == nil {
  102. return errors.TraceNew("unexpected consume return values")
  103. }
  104. // Test: incomplete padding
  105. sender, err = NewMeekRequestPayloadPaddingState(key, cookie, 0.0, 1, 1)
  106. if err != nil {
  107. return errors.Trace(err)
  108. }
  109. receiver, err = NewMeekRequestPayloadPaddingState(key, cookie, 0.0, 0, 0)
  110. if err != nil {
  111. return errors.Trace(err)
  112. }
  113. paddingHeader, err = sender.SenderGetNextPadding(true)
  114. if err != nil {
  115. return errors.Trace(err)
  116. }
  117. if len(paddingHeader) < 4 {
  118. return errors.TraceNew("unexpected padded header length")
  119. }
  120. bytesRead, morePadding, err = receiver.ReceiverConsumePadding(
  121. bytes.NewReader(paddingHeader[:3]))
  122. if bytesRead != 3 || !morePadding || err == nil {
  123. return errors.TraceNew("unexpected consume return values")
  124. }
  125. // Test: round trips
  126. const (
  127. roundTrips = 1000
  128. emptyPayloadProbability = 0.5
  129. requestMinSize = 1
  130. requestMaxSize = 131072
  131. responseMinSize = 1
  132. responseMaxSize = 131072
  133. omitPaddingProbability = 0.5
  134. minPaddingSize = 1
  135. maxPaddingSize = 65533
  136. )
  137. clientRequestPaddingState, err := NewMeekRequestPayloadPaddingState(
  138. key, cookie, omitPaddingProbability, minPaddingSize, maxPaddingSize)
  139. if err != nil {
  140. return errors.Trace(err)
  141. }
  142. serverRequestPaddingState, err := NewMeekRequestPayloadPaddingState(
  143. key, cookie, 0.0, 0, 0)
  144. if err != nil {
  145. return errors.Trace(err)
  146. }
  147. serverResponsePaddingState, err := NewMeekResponsePayloadPaddingState(
  148. key, cookie, omitPaddingProbability, minPaddingSize, maxPaddingSize)
  149. if err != nil {
  150. return errors.Trace(err)
  151. }
  152. clientResponsePaddingState, err := NewMeekResponsePayloadPaddingState(
  153. key, cookie, 0.0, 0, 0)
  154. if err != nil {
  155. return errors.Trace(err)
  156. }
  157. for i := 0; i < roundTrips; i++ {
  158. // Client sends potentially padded request to server.
  159. requestSize := 0
  160. if !prng.FlipWeightedCoin(emptyPayloadProbability) {
  161. requestSize = prng.Range(requestMinSize, requestMaxSize)
  162. }
  163. requestPaddingHeader, err := clientRequestPaddingState.SenderGetNextPadding(
  164. requestSize == 0)
  165. if err != nil {
  166. return errors.Trace(err)
  167. }
  168. if requestSize > 0 {
  169. if len(requestPaddingHeader) != 1 {
  170. return errors.TraceNew("unexpected request no-padding header")
  171. }
  172. } else {
  173. if len(requestPaddingHeader) != 0 && len(requestPaddingHeader) < 4 {
  174. return errors.TraceNew("unexpected request padding header")
  175. }
  176. }
  177. readRequest := func() error {
  178. if len(requestPaddingHeader) == 0 {
  179. return nil
  180. }
  181. reader := bytes.NewReader(requestPaddingHeader)
  182. failAfterOneByte := prng.FlipCoin()
  183. var r io.Reader
  184. r = reader
  185. if failAfterOneByte {
  186. // Exercise the padding reader state machine by returning at most
  187. // one byte per read.
  188. r = newOneByteReader(reader)
  189. }
  190. for {
  191. bytesRead, morePadding, err := serverRequestPaddingState.ReceiverConsumePadding(r)
  192. if err != nil && !morePadding {
  193. return errors.Trace(err)
  194. }
  195. if failAfterOneByte && bytesRead != 1 {
  196. return errors.Tracef("unexpected request padding 1 bytes read: %d", bytesRead)
  197. }
  198. if !failAfterOneByte && bytesRead != int64(len(requestPaddingHeader)) {
  199. return errors.Tracef("unexpected request padding all bytes read: %d", bytesRead)
  200. }
  201. if !morePadding {
  202. if reader.Len() > 0 {
  203. return errors.TraceNew("unexpected unread request padding")
  204. }
  205. break
  206. }
  207. }
  208. return nil
  209. }
  210. err = readRequest()
  211. if err != nil {
  212. return errors.Trace(err)
  213. }
  214. // Server sends potentially padded response to client.
  215. responseSize := 0
  216. if !prng.FlipWeightedCoin(emptyPayloadProbability) {
  217. responseSize = prng.Range(responseMinSize, responseMaxSize)
  218. }
  219. responsePaddingHeader, err := serverResponsePaddingState.SenderGetNextPadding(
  220. responseSize == 0)
  221. if err != nil {
  222. return errors.Trace(err)
  223. }
  224. if responseSize > 0 {
  225. if len(responsePaddingHeader) != 1 {
  226. return errors.TraceNew("unexpected response no-padding header")
  227. }
  228. } else {
  229. if len(responsePaddingHeader) != 0 && len(responsePaddingHeader) < 4 {
  230. return errors.TraceNew("unexpected response padding header")
  231. }
  232. }
  233. readResponse := func() error {
  234. if len(responsePaddingHeader) == 0 {
  235. return nil
  236. }
  237. reader := bytes.NewReader(responsePaddingHeader)
  238. failAfterOneByte := prng.FlipCoin()
  239. var r io.Reader
  240. r = reader
  241. if failAfterOneByte {
  242. // Exercise the padding reader state machine by returning at most
  243. // one byte per read.
  244. r = newOneByteReader(reader)
  245. }
  246. for {
  247. bytesRead, morePadding, err := clientResponsePaddingState.ReceiverConsumePadding(r)
  248. if err != nil && !morePadding {
  249. return errors.Trace(err)
  250. }
  251. if failAfterOneByte && bytesRead != 1 {
  252. return errors.Tracef("unexpected response padding 1 bytes read: %d", bytesRead)
  253. }
  254. if !failAfterOneByte && bytesRead != int64(len(responsePaddingHeader)) {
  255. return errors.Tracef("unexpected response padding all bytes read: %d", bytesRead)
  256. }
  257. if !morePadding {
  258. if reader.Len() > 0 {
  259. return errors.TraceNew("unexpected unread response padding")
  260. }
  261. break
  262. }
  263. }
  264. return nil
  265. }
  266. err = readResponse()
  267. if err != nil {
  268. return errors.Trace(err)
  269. }
  270. }
  271. return nil
  272. }
  273. func FuzzMeekPayloadPaddingReceiverConsume(f *testing.F) {
  274. // Test: ReceiverConsumePadding handles fuzzed inputs without panicking.
  275. f.Add(true, 0, 0)
  276. f.Add(false, 0, 0)
  277. f.Add(true, 255, 3)
  278. f.Fuzz(func(t *testing.T, addPadding bool, mutate int, truncate int) {
  279. err := runFuzzMeekPayloadPaddingReceiverConsume(addPadding, mutate, truncate)
  280. if err != nil {
  281. t.Fatal(err.Error())
  282. return
  283. }
  284. })
  285. }
  286. func runFuzzMeekPayloadPaddingReceiverConsume(
  287. addPadding bool, mutate int, truncate int) error {
  288. key := prng.HexString(16)
  289. cookie := prng.HexString(16)
  290. for i := 0; i < 2; i++ {
  291. var sender, receiver *MeekPayloadPaddingState
  292. var err error
  293. if i == 0 {
  294. sender, err = NewMeekRequestPayloadPaddingState(key, cookie, 0.0, 1, 256)
  295. if err != nil {
  296. return errors.Trace(err)
  297. }
  298. receiver, err = NewMeekRequestPayloadPaddingState(key, cookie, 0.0, 0, 0)
  299. if err != nil {
  300. return errors.Trace(err)
  301. }
  302. } else {
  303. sender, err = NewMeekResponsePayloadPaddingState(key, cookie, 0.0, 1, 256)
  304. if err != nil {
  305. return errors.Trace(err)
  306. }
  307. receiver, err = NewMeekResponsePayloadPaddingState(key, cookie, 0.0, 0, 0)
  308. if err != nil {
  309. return errors.Trace(err)
  310. }
  311. }
  312. payload, err := sender.SenderGetNextPadding(addPadding)
  313. if err != nil {
  314. return errors.Trace(err)
  315. }
  316. input := append([]byte(nil), payload...)
  317. if len(input) > 0 {
  318. cut := uint(truncate) % uint(len(input)+1)
  319. input = input[:cut]
  320. }
  321. if len(input) > 0 && mutate != 0 {
  322. input[prng.Intn(len(input))] ^= byte(mutate)
  323. }
  324. _, _, _ = receiver.ReceiverConsumePadding(bytes.NewReader(input))
  325. }
  326. return nil
  327. }
  328. type oneByteReader struct {
  329. reader io.Reader
  330. fail bool
  331. }
  332. func newOneByteReader(reader io.Reader) *oneByteReader {
  333. return &oneByteReader{
  334. reader: reader,
  335. }
  336. }
  337. func (r *oneByteReader) Read(p []byte) (int, error) {
  338. if r.fail {
  339. r.fail = false
  340. return 0, io.EOF
  341. }
  342. n, err := r.reader.Read(p[0:1])
  343. r.fail = true
  344. return n, err
  345. }